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

Feat struct log #2179

Merged
merged 28 commits into from May 14, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d0307ea
Log storm to its own logger.
vEpiphyte May 3, 2021
3c6bc46
Structlog POC
vEpiphyte May 4, 2021
022813c
Correct filename
vEpiphyte May 4, 2021
431577a
Merge branch 'master' into feat_struct_log
vEpiphyte May 5, 2021
5d18ada
Add structlog test
vEpiphyte May 5, 2021
3330a18
Tweak test
vEpiphyte May 5, 2021
3b46c68
Fix tests, skip a test
vEpiphyte May 5, 2021
6fb200b
Branch build.
vEpiphyte May 5, 2021
617e10f
Use envbool
vEpiphyte May 5, 2021
1553ffd
Fix envbool
vEpiphyte May 5, 2021
30eb24b
Tweak attribute name for datadog
vEpiphyte May 6, 2021
419209a
Merge branch 'master' into feat_struct_log
vEpiphyte May 6, 2021
edeedbb
Fix unit test
vEpiphyte May 6, 2021
cad1474
Add unicode test, remove dead code.
vEpiphyte May 6, 2021
aa68b64
Use s_common.err()
vEpiphyte May 7, 2021
3dabd69
Protect against unserializable types.
vEpiphyte May 7, 2021
f9348d3
Nest exception information and logger information
vEpiphyte May 10, 2021
639d700
Add a generic command line hook; interpret it right away for setting …
vEpiphyte May 11, 2021
9170de7
Fix typo
vEpiphyte May 11, 2021
85103f9
Fix hideconf setting
vEpiphyte May 11, 2021
e875d0c
fix unit test mock
vEpiphyte May 11, 2021
cca01f0
Merge branch 'master' into feat_struct_log
vEpiphyte May 12, 2021
8fbcb6a
Merge branch 'master' into feat_struct_log
vEpiphyte May 13, 2021
3cc405b
Fix up test_cell_confprint
vEpiphyte May 13, 2021
8bc6dd2
Rip out extra outp usage.
vEpiphyte May 13, 2021
3a8bbdb
Remove thread from log record
vEpiphyte May 13, 2021
2eec576
Coverage
vEpiphyte May 13, 2021
41a14ed
Merge branch 'master' into feat_struct_log
invisig0th May 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Expand Up @@ -742,6 +742,7 @@ workflows:
branches:
only:
- master
- feat_struct_log

- gh-release/dorelease:
requires:
Expand Down
12 changes: 11 additions & 1 deletion synapse/common.py
Expand Up @@ -30,6 +30,7 @@
import synapse.exc as s_exc
import synapse.lib.const as s_const
import synapse.lib.msgpack as s_msgpack
import synapse.lib.structlog as s_structlog

class NoValu:
pass
Expand Down Expand Up @@ -658,14 +659,23 @@ def setlogging(mlogger, defval=None):
Returns:
None
'''
log_struct = envbool('SYN_LOG_STRUCT', 'false')
invisig0th marked this conversation as resolved.
Show resolved Hide resolved
log_level = os.getenv('SYN_LOG_LEVEL',
defval)
if log_level: # pragma: no cover
if isinstance(log_level, str):
log_level = log_level.upper()
if log_level not in s_const.LOG_LEVEL_CHOICES:
raise ValueError('Invalid log level provided: {}'.format(log_level))
logging.basicConfig(level=log_level, format=s_const.LOG_FORMAT)

if log_struct:
handler = logging.StreamHandler()
formatter = s_structlog.JsonFormatter()
handler.setFormatter(formatter)
logging.basicConfig(level=log_level, handlers=(handler,))

else:
logging.basicConfig(level=log_level, format=s_const.LOG_FORMAT)
mlogger.info('log level set to %s', log_level)

syndir = os.getenv('SYN_DIR')
Expand Down
4 changes: 3 additions & 1 deletion synapse/cortex.py
Expand Up @@ -58,6 +58,7 @@
import synapse.lib.stormlib.modelext as s_stormlib_modelext # NOQA

logger = logging.getLogger(__name__)
stormlogger = logging.getLogger('synapse.storm')

'''
A Cortex implements the synapse hypergraph object.
Expand Down Expand Up @@ -4226,7 +4227,8 @@ def _logStormQuery(self, text, user):
'''
if self.conf.get('storm:log'):
lvl = self.conf.get('storm:log:level')
logger.log(lvl, 'Executing storm query {%s} as [%s]', text, user.name)
stormlogger.log(lvl, 'Executing storm query {%s} as [%s]', text, user.name,
extra={'synapse': {'text': text, 'username': user.name, 'user': user.iden}})

async def getNodeByNdef(self, ndef, view=None):
'''
Expand Down
20 changes: 13 additions & 7 deletions synapse/lib/cell.py
Expand Up @@ -1961,33 +1961,39 @@ async def initFromArgv(cls, argv, outp=None):

if 'dmon:listen' not in cell.conf:
await cell.dmon.listen(opts.telepath)
if outp is not None:
outp.printf(f'...{cell.getCellType()} API (telepath): %s' % (opts.telepath,))
# if outp is not None:
vEpiphyte marked this conversation as resolved.
Show resolved Hide resolved
# outp.printf(f'...{cell.getCellType()} API (telepath): %s' % (opts.telepath,))
logger.info(f'...{cell.getCellType()} API (telepath): {opts.telepath}')
else:

if outp is not None:
lisn = cell.conf.get('dmon:listen')
if lisn is None:
lisn = cell.getLocalUrl()

outp.printf(f'...{cell.getCellType()} API (telepath): %s' % (lisn,))
# outp.printf(f'...{cell.getCellType()} API (telepath): %s' % (lisn,))
logger.info(f'...{cell.getCellType()} API (telepath): %s' % (lisn,))

if 'https:port' not in cell.conf:
await cell.addHttpsPort(opts.https)
if outp is not None:
outp.printf(f'...{cell.getCellType()} API (https): %s' % (opts.https,))
# outp.printf(f'...{cell.getCellType()} API (https): %s' % (opts.https,))
logger.info(f'...{cell.getCellType()} API (https): %s' % (opts.https,))
else:
if outp is not None:
port = cell.conf.get('https:port')
if port is None:
outp.printf(f'...{cell.getCellType()} API (https): disabled')
# outp.printf(f'...{cell.getCellType()} API (https): disabled')
logger.info(f'...{cell.getCellType()} API (https): disabled')
else:
outp.printf(f'...{cell.getCellType()} API (https): %s' % (port,))
# outp.printf(f'...{cell.getCellType()} API (https): %s' % (port,))
logger.info(f'...{cell.getCellType()} API (https): %s' % (port,))

if opts.name is not None:
cell.dmon.share(opts.name, cell)
if outp is not None:
outp.printf(f'...{cell.getCellType()} API (telepath name): %s' % (opts.name,))
# outp.printf(f'...{cell.getCellType()} API (telepath name): %s' % (opts.name,))
logger.info(f'...{cell.getCellType()} API (telepath name): %s' % (opts.name,))

except (asyncio.CancelledError, Exception):
await cell.fini()
Expand Down
37 changes: 37 additions & 0 deletions synapse/lib/structlog.py
@@ -0,0 +1,37 @@
import json

import logging

class JsonFormatter(logging.Formatter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def format(self, record: logging.LogRecord):

record.message = record.getMessage()
mesg = self.formatMessage(record)
ret = {
'message': mesg,
'logger': record.name,
'thread': record.threadName,
'process': record.processName,
'filename': record.filename,
'level': record.levelname,
'func': record.funcName,
'time': self.formatTime(record, self.datefmt),
}
if record.exc_info:
ret['exc_info'] = self.formatException(record.exc_info)
vEpiphyte marked this conversation as resolved.
Show resolved Hide resolved
if record.exc_text:
ret['exc_text'] = record.exc_text
if record.stack_info:
ret['stack_info'] = record.stack_info

# stuffing our extra into a single dictionary avoids a loop
# over record.__dict__ extracting fields which are not known
# attributes for each log record.
extras = record.__dict__.get('synapse')
if extras:
ret.update({k: v for k, v in extras.items() if k not in ret})

return json.dumps(ret)
2 changes: 1 addition & 1 deletion synapse/tests/test_cortex.py
Expand Up @@ -2170,7 +2170,7 @@ async def test_remote_storm(self):
# Remote storm test paths
async with self.getTestCoreAndProxy() as (realcore, core):
# Storm logging
with self.getAsyncLoggerStream('synapse.cortex', 'Executing storm query {help ask} as [root]') \
with self.getAsyncLoggerStream('synapse.storm', 'Executing storm query {help ask} as [root]') \
as stream:
await alist(core.storm('help ask'))
self.true(await stream.wait(4))
Expand Down
1 change: 1 addition & 0 deletions synapse/tests/test_lib_cell.py
Expand Up @@ -627,6 +627,7 @@ async def test_cell_hiveapi(self):
self.eq((('bar', 10), ('baz', 30)), await proxy.getHiveKeys(('foo',)))

async def test_cell_confprint(self):
self.skip('Skip for the moment. Relies on split output streams.')

with self.withSetLoggingMock():

Expand Down
69 changes: 69 additions & 0 deletions synapse/tests/test_lib_structlog.py
@@ -0,0 +1,69 @@
import io
import json
import logging

import synapse.common as s_common
import synapse.lib.structlog as s_structlog

import synapse.tests.utils as s_test

logger = logging.getLogger(__name__)

class StructLogTest(s_test.SynTest):

def test_structlog(self):
stream = io.StringIO()
handler = logging.StreamHandler(stream=stream)
formatter = s_structlog.JsonFormatter()
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.warning('Test message 1')
logger.error('Test message 2')
iden = s_common.guid()
logger.error('Extra test', extra={'synapse': {'foo': 'bar', 'iden': iden, 'time': 0}})

try:
_ = 1 / 0
except ZeroDivisionError:
logger.exception('Exception handling')

logger.warning('Unicode is cool for 程序员!')

data = stream.getvalue()

# There is a trailing \n on the stream
raw_mesgs = [m for m in data.split('\n') if m]
mesgs = [json.loads(m) for m in raw_mesgs]
self.len(5, mesgs)

mesg = mesgs[0]
self.eq(set(mesg.keys()),
{'message', 'logger', 'thread', 'process', 'thread',
'filename', 'level', 'func', 'time'})
self.eq(mesg.get('message'), 'Test message 1')
self.eq(mesg.get('level'), 'WARNING')

mesg = mesgs[1]
self.eq(mesg.get('message'), 'Test message 2')
self.eq(mesg.get('level'), 'ERROR')

mesg = mesgs[2]
self.eq(mesg.get('message'), 'Extra test')
self.eq(mesg.get('level'), 'ERROR')
self.eq(mesg.get('foo'), 'bar')
self.eq(mesg.get('iden'), iden)
self.ne(mesg.get('time'), 0) # time was not overwritten by the extra

mesg = mesgs[3]
self.eq(mesg.get('message'), 'Exception handling')
self.eq(mesg.get('level'), 'ERROR')
exc_info = mesg.get('exc_info')
self.isin('Traceback', exc_info)
self.isin('_ = 1 / 0', exc_info)
self.isin('ZeroDivisionError: division by zero', exc_info)

mesg = mesgs[4]
rawm = raw_mesgs[4]
self.isin(r'Unicode is cool for \u7a0b\u5e8f\u5458!', rawm)
self.eq(mesg.get('message'), 'Unicode is cool for 程序员!')