diff --git a/chatbot/python/chatbot.py b/chatbot/python/chatbot.py index d70548fb..9bbe191a 100644 --- a/chatbot/python/chatbot.py +++ b/chatbot/python/chatbot.py @@ -27,6 +27,10 @@ from google.protobuf.json_format import MessageToDict from tinode_grpc import pb from tinode_grpc import pbx +# For compatibility with python2 +if sys.version_info[0] >= 3: + unicode = str + APP_NAME = "Tino-chatbot" APP_VERSION = "1.2.0" LIB_VERSION = pkg_resources.get_distribution("tinode_grpc").version @@ -51,11 +55,20 @@ def add_future(tid, bundle): onCompletion[tid] = bundle # Shorten long strings for logging. -class JsonHelper(json.JSONEncoder): - def default(self, obj): - if type(obj) == str and len(obj) > MAX_LOG_LEN: - return '<' + len(obj) + ', bytes: ' + obj[:12] + '...' + obj[-12:] + '>' - return super(JsonHelper, self).default(obj) +def clip_long_string(obj): + if isinstance(obj, unicode) or isinstance(obj, str): + if len(obj) > MAX_LOG_LEN: + return '<' + str(len(obj)) + ' bytes: ' + obj[:12] + '...' + obj[-12:] + '>' + return obj + elif isinstance(obj, (list, tuple)): + return [clip_long_string(item) for item in obj] + elif isinstance(obj, dict): + return dict((key, clip_long_string(val)) for key, val in obj.items()) + else: + return obj + +def to_json(msg): + return json.dumps(clip_long_string(MessageToDict(msg))) # Resolve or reject the future def exec_future(tid, code, text, params): @@ -144,7 +157,7 @@ def client_generate(): msg = queue_out.get() if msg == None: return - log("out:", json.dumps(MessageToDict(msg), cls=JsonHelper)) + log("out:", to_json(msg) yield msg def client_post(msg): @@ -237,8 +250,7 @@ def client_message_loop(stream): try: # Read server responses for msg in stream: - log(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3], - "in:", json.dumps(MessageToDict(msg), cls=JsonHelper)) + log(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3], "in:", to_json(msg)) if msg.HasField("ctrl"): # Run code on command completion diff --git a/tn-cli/tn-cli.py b/tn-cli/tn-cli.py index 2bda8bc3..5880facf 100644 --- a/tn-cli/tn-cli.py +++ b/tn-cli/tn-cli.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """Python implementation of Tinode command line client using gRPC.""" @@ -27,8 +28,6 @@ import sys import threading import time -from google.protobuf import json_format - # Import generated grpc modules from tinode_grpc import pb from tinode_grpc import pbx @@ -37,6 +36,7 @@ import tn_globals from tn_globals import printerr from tn_globals import printout from tn_globals import stdoutln +from tn_globals import to_json APP_NAME = "tn-cli" APP_VERSION = "1.4.0" @@ -821,7 +821,10 @@ def gen_message(scheme, secret, args): tn_globals.InputThread.daemon = True tn_globals.InputThread.start() - yield hiMsg(id) + msg = hiMsg(id) + if tn_globals.Verbose: + stdoutln("\r=> " + to_json(msg)) + yield msg if scheme != None: id += 1 @@ -829,7 +832,10 @@ def gen_message(scheme, secret, args): setattr(login, 'scheme', scheme) setattr(login, 'secret', secret) setattr(login, 'cred', None) - yield loginMsg(id, login, args) + msg = loginMsg(id, login, args) + if tn_globals.Verbose: + stdoutln("\r=> " + to_json(msg)) + yield msg print_prompt = True @@ -859,6 +865,8 @@ def gen_message(scheme, secret, args): tn_globals.WaitingFor = cmd if not hasattr(cmd, 'no_yield'): + if tn_globals.Verbose: + stdoutln("\r=> " + to_json(pbMsg)) yield pbMsg elif not tn_globals.OutputQueue.empty(): @@ -920,6 +928,9 @@ def run(args, schema, secret): # Read server responses for msg in stream: + if tn_globals.Verbose: + stdoutln("\r<= " + to_json(msg)) + if msg.HasField("ctrl"): handle_ctrl(msg.ctrl) @@ -1044,12 +1055,17 @@ if __name__ == '__main__': parser.add_argument('--api-key', default='AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K', help='API key for file uploads') parser.add_argument('--load-macros', default='./macros.py', help='path to macro module to load') parser.add_argument('--version', action='store_true', help='print version') + parser.add_argument('--verbose', action='store_true', help='verbose output: print full JSON representation of messages') + args = parser.parse_args() if args.version: printout(version) exit() + if args.verbose: + tn_globals.Verbose = True + printout(purpose) printout("Secure server" if args.ssl else "Server", "at '"+args.host+"'", "SNI="+args.ssl_host if args.ssl_host else "") diff --git a/tn-cli/tn_globals.py b/tn-cli/tn_globals.py index e77712c9..de2b51c1 100644 --- a/tn-cli/tn_globals.py +++ b/tn-cli/tn_globals.py @@ -2,13 +2,19 @@ # To make print() compatible between p2 and p3 from __future__ import print_function +import json import sys from collections import deque +from google.protobuf.json_format import MessageToDict try: import Queue as queue except ImportError: import queue +if sys.version_info[0] >= 3: + # for compatibility with python2 + unicode = str + # Dictionary wich contains lambdas to be executed when server {ctrl} response is received. OnCompletion = {} @@ -35,6 +41,9 @@ DefaultTopic = None # Variables: results of command execution Variables = {} +# Flag to enable extended logging. Useful for debugging. +Verbose = False + # Print prompts in interactive mode only. def printout(*args): if IsInteractive: @@ -65,3 +74,27 @@ def stdout(*args): def stdoutln(*args): args = args + ("\n",) stdout(*args) + +# Shorten long strings for logging. +def clip_long_string(obj): + if isinstance(obj, str) or isinstance(obj, unicode): + if len(obj) > 64: + return '<' + str(len(obj)) + ' bytes: ' + obj[:12] + '...' + obj[-12:] + '>' + return obj + elif isinstance(obj, (list, tuple)): + return [clip_long_string(item) for item in obj] + elif isinstance(obj, dict): + return dict((key, clip_long_string(val)) for key, val in obj.items()) + else: + return obj + +# Convert protobuff message to json. Shorten very long strings. +def to_json(msg): + if not msg: + return 'null' + try: + return json.dumps(clip_long_string(MessageToDict(msg))) + except Exception as err: + stdoutln("Exception: {}".format(err)) + + return 'exception'