In my python script, I am using the logging module with INFO going to a file and the screen:
fh_info = logging.FileHandler(info)
fh_info.setLevel(logging.INFO)
fh_info.setFormatter(formatter)
std_out_info = logging.StreamHandler()
std_out_info.setLevel(logging.INFO)
logger.addHandler(fh_info)
logger.addHandler(std_out_info)
But the issue I am having is that the messages got to the screen after the function is completed. For instance, in:
def getToken(self):
msg = ("Initializing token....")
logger.info(msg)
sys.stdout.flush()
try:
jsonreq = ( {"auth": {"KEY:apiKeyCredentials": {
"username": self.username,
"apiKey": self.apikey}}})
auth_headers = {'content-type': 'application/json'}
r = requests.post(self.url, data=json.dumps(jsonreq), headers=auth_headers)
self.jsonresp = json.loads(r.text)
self.token = str(self.jsonresp['access']['token']['id'])
msg = ("done!")
logger.info(msg)
except:
msg = ("Bad name or apikey!")
logger.error(msg)
sys.exit()
The message "Initializing token...." will go to the screen after the operations in the function are completed. The makes for a long pause while authentication is going on with no output...then after authentication completes I see "Initializing token....done".
How can I make the logger.info(msg) stay in sync with the flow of the script and output to the screen in it's timely manner?
You are not initialising the StreamHandler with sys.stdout, so it will use the documented default - sys.stderr. That's why your flushing of sys.stdout isn't having any effect - there's nothing the the code you posted which is causing writes to sys.stdout!
Related
Im trying to create GUI using tkinter, objective is to call a function from Button(Tkinter Window), without websocket function , i'm able to get Tkinter window and able to execute the function via button, trying the same with Websocket client function, functions are executed at first and Tkinter window is not showing UP.
from tkinter import *
from threading import *
import websocket
import threading
window=Tk()
window.title("Scalp")
window.geometry('400x400')
window.config(bg='lavender')
def Login():
import requests
import json
global tokens
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
url = 'https://api.stocknote.com/login'
myobj = { 'userId': 'DDXX12', 'password': 'Durant', 'yob': '1999'}
res = requests.post(url, data = json.dumps(myobj), headers = headers)
tokens = res.json().get("sessionToken")
print (res.text);
print(tokens)
def on_message(ws, msg):
print ("Message Arrived:" + msg)
return
def on_error(ws, error):
print (error)
return
def on_close(ws):
print ("Connection Closed")
return
def on_open(ws):
print ("Sending json")
data='{"request":{"streaming_type":"quote", "data":{"symbols":[{"symbol":"45402_NFO"}]}, "request_type":"subscribe", "response_format":"json"}}'
ws.send(data)
ws.send("\n")
return
def connection():
Login()
headers = {'x-session-token': tokens }
websocket.enableTrace(True)
ws = websocket.WebSocketApp("wss://stream.stocknote.com", on_open = on_open, on_message = on_message, on_error = on_error, on_close = on_close, header = headers)
ws.run_forever()
return
a=Button(window,text='Login',width=12,bg='azure',command=Login(),fg='black',font('bold',14),activebackground='dark sea green',activeforeground='khaki3')
a.place(x=100,y=50)
b=Button(window,text='BankNifty',width=12,bg='azure',command=connection(),fg='black',font('bold',14),activebackground='dark sea green',activeforeground='khaki3')
b.place(x=100,y=90)
window.mainloop
Tkinter window is not appearing , but functions are executed directly.
There are several problems in your code.
First, you aren't calling mainloop. You need to change window.mainloop to be window.mainloop().
The second problem is that you are immediately calling connection and Login at startup because you are specifying them wrong.
When creating a button, you must pass a callable. In your case you're immediately calling the function and then passing the result to the command option. You need to change command=Login() to command=Login, and change command=connection() to command=connection.
Finally, when connection is called, it calls ws.run_forever(). That is a blocking call - it doesn't return. Because it doesn't return, the tkinter event loop (what gets started with mainloop()) never has a chance to run. Because it doesn't run, it can't process even basic events like requests to display the window.
I am trying to get the message that is being returned by and apparently it is more of a
<function on_message at 0x00000138D6488F28>
How can I get it to return json message instead of the one above?
Below is my code.
from websocket import WebSocketApp
from json import dumps, loads
from pprint import pprint
URL = "wss://ws-feed.gdax.com"
def on_message(_, message):
pprint(loads(message))
print
def on_open(socket):
params = {
"type": "subscribe",
"channels": [{"name": "ticker", "product_ids": ["BTC-EUR"]}]
}
socket.send(dumps(params))
def main():
ws = WebSocketApp(URL, on_open=on_open, on_message=on_message)
while True:
print(ws.on_message)
time.sleep(1)
if __name__ == '__main__':
main()
def on_message(_, message): # this will be called everytime a message is recieved
pprint(loads(message))
print
def main():
ws = WebSocketApp(URL, on_open=on_open, on_message=on_message)
ws.run_forever() # this will run the ws main_loop that is builtin and listen for the connection and messages
ps thats a cool ws service ... I always enjoy finding new public ws services
Well, it prints function, because you literally are printing out the function, not calling it. But that function is not meant to be called by you, but it will be called by the WebSocketApp when you actually have a message and someone has connected to your socket.
If you, however, really want to call it, you can probably just do it by changing print(ws.on_message) to ws.on_message(None, 'your message').
I have a multi-room speaker system from Denon called Heos which I want to control by using python script. To communicate with the multi-room system I have to telnet to port 1255 on the device and send commands like this:
heos://player/set_play_state?pid=player_id&state=play_state
The response back is in json:
{
"heos": {
"command": " player/set_play_state ",
"result": "success",
"message": "pid='player_id'&state='play_state'"
}
}
I have successfully used python telnet lib to send simple commands like this:
command = "heos://player/set_play_state?pid=player_id&state=play_state"
telnet.write(command.encode('ASCII') + b'\r\n')
But what is the best way to get the response back in a usable format? Loop with telnet.read_until? I want to result and message lines back to a clean variable.
This method with using telnet to communicate with api feels a bit dirty. Is it possible to use something else, for example socket?
Thanks in advance
The API/CLI is documented here: http://rn.dmglobal.com/euheos/HEOS_CLI_ProtocolSpecification.pdf
While it may be possible to use loop_until() here, it would depend on exactly how the response JSON is formatted, and it would probably be unwise to rely on it.
If the remote device closes the connection after sending the response, the easy way would be a simple
response = json.loads(telnet.read_all().decode())
If it remains open for more commands, then you'll instead need to keep receiving until you have a complete JSON object. Here's a possibility that just keeps trying to parse the JSON until it succeeds:
response = ''
while True:
response += telnet.read_some().decode()
try:
response = json.loads(response)
break
except ValueError:
pass
Either way, your result and message are response['heos']['result'] and response['heos']['message'].
FWIW, here is my GitHub repo (inspired by this repo) for controlling a HEOS speaker with Python. It uses a similar approach as the accepted result, but additionally waits if the HEOS player is busy.
def telnet_request(self, command, wait = True):
"""Execute a `command` and return the response(s)."""
command = self.heosurl + command
logging.debug("telnet request {}".format(command))
self.telnet.write(command.encode('ASCII') + b'\r\n')
response = b''
while True:
response += self.telnet.read_some()
try:
response = json.loads(response)
if not wait:
logging.debug("I accept the first response: {}".format(response))
break
# sometimes, I get a response with the message "under
# process". I might want to wait here
message = response.get("heos", {}).get("message", "")
if "command under process" not in message:
logging.debug("I assume this is the final response: {}".format(response))
break
logging.debug("Wait for the final response")
response = b'' # forget this message
except ValueError:
# response is not a complete JSON object
pass
except TypeError:
# response is not a complete JSON object
pass
if response.get("result") == "fail":
logging.warn(response)
return None
return response
My RequestHandler looks like this:
class GetChart(webapp2.RequestHandler):
def post(self):
station_id = cgi.escape(self.request.get('id'))
request_time = cgi.escape(self.request.get('time'))
request_date_string = cgi.escape(self.request.get('date'))
request_date = datetime.strptime(
request_date_string, "%m/%d/%Y")
# Build the URL string. (Details removed.)
url_string = ****
logging.info(url_string)
sock = urllib2.urlopen(url_string)
data = sock.read()
sock.close()
for line in data.split('\n'):
# If I put 'pass' here instead, there's no delay between
# the 'Returning' and the response actually being sent.
logging.info(line)
result_json = {'status': 'Not implemented'}
self.response.out.write(json.dumps(result_json))
logging.info('Returning.')
logging.basicConfig(level=logging.INFO)
app = webapp2.WSGIApplication([('/', MainPage),
('/submit', GetChart)],
debug=True)
In my logs, I see this:
INFO 2011-11-26 19:41:44,243 atmosview.py:131] Returning.
INFO 2011-11-26 19:41:51,331 dev_appserver.py:2753] "POST /submit HTTP/1.1" 200 -
That's a 7 second delay between the 'Returning' being logged, and the response actually being sent. Why is this?
I also notice that if I replace the logging.info(line) with pass, the delay is much less: about 0.2 seconds.
Is there something with the logging module that causes the function to not actually return immediately, especially when a lot of calls have been made to logging.info()?
It looks like this may be a bug in the AppEngine 1.6.0 dev_appserver: code.google.com/p/googleappengine/issues/detail?id=6315
I have the logging module MemoryHandler set up to queue debug and error messages for the SMTPHandler target. What I want is for an email to be sent when the process errors that contains all debug statements up to that point (one per line). What I get instead is a separate email for every debug message.
This seems like it should be trivial, and part of the logging package, but I can't find anything about it, no examples, nothing on Google.
log = logging.getLogger()
log.setLevel(logging.DEBUG)
debug_format = logging.Formatter("%(levelname)s at %(asctime)s in %(filename)s (line %(lineno)d):: %(message)s")
# write errors to email
error_mail_subject = "ERROR: Script error in %s on %s" % (sys.argv[0], os.uname()[1])
error_mail_handler = logging.handlers.SMTPHandler(SMTP_HOST, 'errors#'+os.uname()[1], [LOG_EMAIL], error_mail_subject)
error_mail_handler.setLevel(logging.ERROR)
#error_mail_handler.setLevel(logging.DEBUG)
error_mail_handler.setFormatter(debug_format)
# buffer debug messages so they can be sent with error emails
memory_handler = logging.handlers.MemoryHandler(1024*10, logging.ERROR, error_mail_handler)
memory_handler.setLevel(logging.DEBUG)
# attach handlers
log.addHandler(memory_handler)
log.addHandler(error_mail_handler)
Related to this:
Do I need to add the error_mail_handler to the logger explicitly if it is a target of memory_handler anyway?
Should error_mail_handler be set to DEBUG or ERROR target? Does it even need a target when it is being fed from memory_handler?
Would love to see some working code from anyone who has solved this problem.
You might want to use or adapt the BufferingSMTPHandler which is in this test script.
In general, you don't need to add a handler to a logger if it's the target of a MemoryHandler handler which has been added to a logger. If you set the level of a handler, that will affect what the handler actually processes - it won't process anything which is less severe than its level setting.
Instead of buffering for email, consider posting unbuffered to a message stream on a messaging app, e.g. on Matrix, Discord, Slack, etc. Having said that, I wrote my own beastly thread-safe implementation of BufferingSMTPHandler (backup link) which sends emails from a separate thread. The primary goal is to not block the main thread.
As written, it uses two queues - this seemed necessary in order to implement some useful class-level parameters that are defined in the "Configurable parameters" section of the code. Although you can use the code as-is, it's probably better if you study and use it to write your own class.
Issues:
Some class-level parameters can perhaps be instance-level instead.
Either threading.Timer or the signal module could perhaps be used to avoid loops that run forever.
If you are using django - here is simple buffering handler, which will use standard django email methods:
import logging
from django.conf import settings
from django.core.mail import EmailMessage
class DjangoBufferingSMTPHandler(logging.handlers.BufferingHandler):
def __init__(self, capacity, toaddrs=None, subject=None):
logging.handlers.BufferingHandler.__init__(self, capacity)
if toaddrs:
self.toaddrs = toaddrs
else:
# Send messages to site administrators by default
self.toaddrs = zip(*settings.ADMINS)[-1]
if subject:
self.subject = subject
else:
self.subject = 'logging'
def flush(self):
if len(self.buffer) == 0:
return
try:
msg = "\r\n".join(map(self.format, self.buffer))
emsg = EmailMessage(self.subject, msg, to=self.toaddrs)
emsg.send()
except Exception:
# handleError() will print exception info to stderr if logging.raiseExceptions is True
self.handleError(record=None)
self.buffer = []
In django settings.py you will need to configure email and logging like this:
EMAIL_USE_TLS = True
EMAIL_PORT = 25
EMAIL_HOST = '' # example: 'smtp.yandex.ru'
EMAIL_HOST_USER = '' # example: 'user#yandex.ru'
EMAIL_HOST_PASSWORD = ''
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
SERVER_EMAIL = EMAIL_HOST_USER
LOGGING = {
'handlers': {
...
'mail_buffer': {
'level': 'WARN',
'capacity': 9999,
'class': 'utils.logging.DjangoBufferingSMTPHandler',
# optional:
# 'toaddrs': 'admin#host.com'
# 'subject': 'log messages'
}
},
...
}
For this purpose I use the BufferingSMTPHandler suggested by Vinay Sajip with one minor tweak: I set the buffer length to something really big (say 5000 log records) and manualy call the flush method of the handler every some seconds and after checking for internet conectivity.
# init
log_handler1 = BufferingSMTPHandler(
'smtp.host.lala', "from#test.com", ['to#test.com'], 'Log event(s)',5000)
...
logger.addHandler(log_handler1)
...
# main code
...
if internet_connection_ok and seconds_since_last_flush>60:
log_handler1.flush() # send buffered log records (if any)
Updated Vinay Sajip's answer for python3.
import logging
from logging.handlers import BufferingHandler
class BufferingSMTPHandler(BufferingHandler):
def __init__(self, mailhost, fromaddr, toaddrs, subject, capacity):
logging.handlers.BufferingHandler.__init__(self, capacity)
self.mailhost = mailhost
self.mailport = None
self.fromaddr = fromaddr
self.toaddrs = toaddrs
self.subject = subject
self.setFormatter(logging.Formatter("%(asctime)s %(levelname)-5s %(message)s"))
def flush(self):
if len(self.buffer) > 0:
try:
import smtplib
port = self.mailport
if not port:
port = smtplib.SMTP_PORT
smtp = smtplib.SMTP(self.mailhost, port)
msg = '''From: {}\r\nTo: {}\r\nSubject: {}\r\n\r\n'''.format(
self.fromaddr,
",".join(self.toaddrs),
self.subject
)
for record in self.buffer:
s = self.format(record)
print (s)
msg = msg + s + "\r\n"
smtp.sendmail(self.fromaddr, self.toaddrs, msg)
smtp.quit()
except:
self.handleError(None) # no particular record
self.buffer = []
#update for #Anant
if __name__ == '__main__'
buff_smtp_handler=BufferingSMTPHandler(...your args)
buff_smtp_handler.setLevel(logging.ERROR)
handlers=[buff_smtp_handler]
logging.basicConfig(handlers=handlers)
I think the point about the SMTP logger is that it is meant to send out a significant log message functioning as some kind of alert if sent to a human recipient or else to be further processed by an automated recipient.
If a collection of log messages is to be sent by email then that constitutes a report being sent at the end of execution of a task and writing that log to a file and then emailing the file would seem to be a reasonable solution.
I took a look at the basic FileHandler log handler and how to build a mechanism to write to a temp file then attach that temp file when the script exits.
I found the "atexit" module that allows for a method to be registered that will be executed against an object when the script is exiting.
import logging
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
import os
from email import encoders
import uuid
# atexit allows for a method to be set to handle an object when the script exits
import atexit
filename = uuid.uuid4().hex
class MailLogger:
def __init__(self, filePath, smtpDict):
self.filePath = filePath
self.smtpDict = smtpDict
# Generate random file name
filename = '%s.txt' % ( uuid.uuid4().hex )
# Create full filename
filename = '%s/%s' % (filePath,filename)
self.filename = filename
self.fileLogger = logging.getLogger('mailedLog')
self.fileLogger.setLevel(logging.INFO)
self.fileHandler = logging.FileHandler(filename)
self.fileHandler.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
self.fileHandler.setFormatter(formatter)
self.fileLogger.addHandler(self.fileHandler)
atexit.register(self.mailOut)
def mailOut(self):
'''
Script is exiting so time to mail out the log file
"emailSettings": {
"smtpServer" : "smtp.dom.com",
"smtpPort" : 25,
"sender" : "sender#dom.com>",
"recipients" : [
"recipient#dom.com"
],
"subject" : "Email Subject"
},
'''
# Close the file handler
smtpDict = self.smtpDict
self.fileHandler.close()
msg = MIMEMultipart('alternative')
s = smtplib.SMTP(smtpDict["smtpServer"], smtpDict["smtpPort"] )
msg['Subject'] = smtpDict["subject"]
msg['From'] = smtpDict["sender"]
msg['To'] = ','.join(smtpDict["recipients"])
body = 'See attached report file'
content = MIMEText(body, 'plain')
msg.attach(content)
attachment = MIMEBase('application', 'octet-stream')
attachment.set_payload(open(self.filename, 'rb').read())
encoders.encode_base64(attachment)
attachment.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(self.filename))
msg.attach(attachment)
s.send_message(msg)
s.quit()
My basic test script is:
from EmailLogRpt import MailLogger
import time
smtpDict = {
"smtpServer" : "smtp.dom.com",
"smtpPort" : 25,
"sender" : "sender#dom.com",
"recipients" : [
"recpient#dom.com>"
],
"subject" : "Email Subject"
}
myMailLogger = MailLogger("/home/ed/tmp",smtpDict).fileLogger
myMailLogger.info("test msg 1")
time.sleep(5)
myMailLogger.info("test msg 2")
Hope this helps somebody.