You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							218 lines
						
					
					
						
							6.8 KiB
						
					
					
				
			
		
		
	
	
							218 lines
						
					
					
						
							6.8 KiB
						
					
					
				# coding: utf-8
 | 
						|
# vim: set ts=4 sw=4 et:
 | 
						|
""" This file contains some utils for connecting to Logentries
 | 
						|
    as well as storing logs in a queue and sending them."""
 | 
						|
 | 
						|
VERSION = '2.0.7'
 | 
						|
 | 
						|
from logentries import helpers as le_helpers
 | 
						|
 | 
						|
import logging
 | 
						|
import threading
 | 
						|
import socket
 | 
						|
import random
 | 
						|
import time
 | 
						|
import sys
 | 
						|
 | 
						|
import certifi
 | 
						|
 | 
						|
 | 
						|
# Size of the internal event queue
 | 
						|
QUEUE_SIZE = 32768
 | 
						|
# Logentries API server address
 | 
						|
LE_API_DEFAULT = "data.logentries.com"
 | 
						|
# Port number for token logging to Logentries API server
 | 
						|
LE_PORT_DEFAULT = 80
 | 
						|
LE_TLS_PORT_DEFAULT = 443
 | 
						|
# Minimal delay between attempts to reconnect in seconds
 | 
						|
MIN_DELAY = 0.1
 | 
						|
# Maximal delay between attempts to recconect in seconds
 | 
						|
MAX_DELAY = 10
 | 
						|
# Unicode Line separator character   \u2028
 | 
						|
LINE_SEP = le_helpers.to_unicode('\u2028')
 | 
						|
 | 
						|
 | 
						|
# LE appender signature - used for debugging messages
 | 
						|
LE = "LE: "
 | 
						|
# Error message displayed when an incorrect Token has been detected
 | 
						|
INVALID_TOKEN = ("\n\nIt appears the LOGENTRIES_TOKEN "
 | 
						|
                 "parameter you entered is incorrect!\n\n")
 | 
						|
 | 
						|
 | 
						|
def dbg(msg):
 | 
						|
    print(LE + msg)
 | 
						|
 | 
						|
 | 
						|
class PlainTextSocketAppender(threading.Thread):
 | 
						|
    def __init__(self, verbose=True, le_api=LE_API_DEFAULT, le_port=LE_PORT_DEFAULT, le_tls_port=LE_TLS_PORT_DEFAULT):
 | 
						|
        threading.Thread.__init__(self)
 | 
						|
 | 
						|
        # Logentries API server address
 | 
						|
        self.le_api = le_api
 | 
						|
 | 
						|
        # Port number for token logging to Logentries API server
 | 
						|
        self.le_port = le_port
 | 
						|
        self.le_tls_port = le_tls_port
 | 
						|
 | 
						|
        self.daemon = True
 | 
						|
        self.verbose = verbose
 | 
						|
        self._conn = None
 | 
						|
        self._queue = le_helpers.create_queue(QUEUE_SIZE)
 | 
						|
 | 
						|
    def empty(self):
 | 
						|
        return self._queue.empty()
 | 
						|
 | 
						|
    def open_connection(self):
 | 
						|
        self._conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
						|
        self._conn.connect((self.le_api, self.le_port))
 | 
						|
 | 
						|
    def reopen_connection(self):
 | 
						|
        self.close_connection()
 | 
						|
 | 
						|
        root_delay = MIN_DELAY
 | 
						|
        while True:
 | 
						|
            try:
 | 
						|
                self.open_connection()
 | 
						|
                return
 | 
						|
            except Exception:
 | 
						|
                if self.verbose:
 | 
						|
                    dbg("Unable to connect to Logentries")
 | 
						|
 | 
						|
            root_delay *= 2
 | 
						|
            if(root_delay > MAX_DELAY):
 | 
						|
                root_delay = MAX_DELAY
 | 
						|
 | 
						|
            wait_for = root_delay + random.uniform(0, root_delay)
 | 
						|
 | 
						|
            try:
 | 
						|
                time.sleep(wait_for)
 | 
						|
            except KeyboardInterrupt:
 | 
						|
                raise
 | 
						|
 | 
						|
    def close_connection(self):
 | 
						|
        if self._conn is not None:
 | 
						|
            self._conn.close()
 | 
						|
 | 
						|
    def run(self):
 | 
						|
        try:
 | 
						|
            # Open connection
 | 
						|
            self.reopen_connection()
 | 
						|
 | 
						|
            # Send data in queue
 | 
						|
            while True:
 | 
						|
                # Take data from queue
 | 
						|
                data = self._queue.get(block=True)
 | 
						|
 | 
						|
                # Replace newlines with Unicode line separator
 | 
						|
                # for multi-line events
 | 
						|
                if not le_helpers.is_unicode(data):
 | 
						|
                    multiline = le_helpers.create_unicode(data).replace(
 | 
						|
                        '\n', LINE_SEP)
 | 
						|
                else:
 | 
						|
                    multiline = data.replace('\n', LINE_SEP)
 | 
						|
                multiline += "\n"
 | 
						|
                # Send data, reconnect if needed
 | 
						|
                while True:
 | 
						|
                    try:
 | 
						|
                        self._conn.send(multiline.encode('utf-8'))
 | 
						|
                    except socket.error:
 | 
						|
                        self.reopen_connection()
 | 
						|
                        continue
 | 
						|
                    break
 | 
						|
        except KeyboardInterrupt:
 | 
						|
            if self.verbose:
 | 
						|
                dbg("Logentries asynchronous socket client interrupted")
 | 
						|
 | 
						|
        self.close_connection()
 | 
						|
 | 
						|
SocketAppender = PlainTextSocketAppender
 | 
						|
 | 
						|
try:
 | 
						|
    import ssl
 | 
						|
    ssl_enabled = True
 | 
						|
except ImportError:  # for systems without TLS support.
 | 
						|
    ssl_enabled = False
 | 
						|
    dbg("Unable to import ssl module. Will send over port 80.")
 | 
						|
else:
 | 
						|
    class TLSSocketAppender(PlainTextSocketAppender):
 | 
						|
 | 
						|
        def open_connection(self):
 | 
						|
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
						|
            sock = ssl.wrap_socket(
 | 
						|
                sock=sock,
 | 
						|
                keyfile=None,
 | 
						|
                certfile=None,
 | 
						|
                server_side=False,
 | 
						|
                cert_reqs=ssl.CERT_REQUIRED,
 | 
						|
                ssl_version=getattr(
 | 
						|
                    ssl,
 | 
						|
                    'PROTOCOL_TLSv1_2',
 | 
						|
                    ssl.PROTOCOL_TLSv1
 | 
						|
                ),
 | 
						|
                ca_certs=certifi.where(),
 | 
						|
                do_handshake_on_connect=True,
 | 
						|
                suppress_ragged_eofs=True,
 | 
						|
            )
 | 
						|
 | 
						|
            sock.connect((self.le_api, self.le_tls_port))
 | 
						|
            self._conn = sock
 | 
						|
 | 
						|
 | 
						|
class LogentriesHandler(logging.Handler):
 | 
						|
    def __init__(self, token, use_tls=True, verbose=True, format=None, le_api=LE_API_DEFAULT, le_port=LE_PORT_DEFAULT, le_tls_port=LE_TLS_PORT_DEFAULT):
 | 
						|
        logging.Handler.__init__(self)
 | 
						|
        self.token = token
 | 
						|
        self.good_config = True
 | 
						|
        self.verbose = verbose
 | 
						|
        # give the socket 10 seconds to flush,
 | 
						|
        # otherwise drop logs
 | 
						|
        self.timeout = 10
 | 
						|
        if not le_helpers.check_token(token):
 | 
						|
            if self.verbose:
 | 
						|
                dbg(INVALID_TOKEN)
 | 
						|
            self.good_config = False
 | 
						|
        if format is None:
 | 
						|
            format = logging.Formatter('%(asctime)s : %(levelname)s, %(message)s',
 | 
						|
                                       '%a %b %d %H:%M:%S %Z %Y')
 | 
						|
        self.setFormatter(format)
 | 
						|
        self.setLevel(logging.DEBUG)
 | 
						|
        if use_tls and ssl_enabled:
 | 
						|
            self._thread = TLSSocketAppender(verbose=verbose, le_api=le_api, le_port=le_port, le_tls_port=le_tls_port)
 | 
						|
        else:
 | 
						|
            self._thread = SocketAppender(verbose=verbose, le_api=le_api, le_port=le_port, le_tls_port=le_tls_port)
 | 
						|
 | 
						|
    def flush(self):
 | 
						|
        # wait for all queued logs to be send
 | 
						|
        now = time.time()
 | 
						|
        while not self._thread.empty():
 | 
						|
            time.sleep(0.2)
 | 
						|
            if time.time() - now > self.timeout:
 | 
						|
                break
 | 
						|
 | 
						|
    def emit_raw(self, msg):
 | 
						|
        if self.good_config and not self._thread.is_alive():
 | 
						|
            try:
 | 
						|
                self._thread.start()
 | 
						|
                if self.verbose:
 | 
						|
                    dbg("Starting Logentries Asynchronous Socket Appender")
 | 
						|
            except RuntimeError: # It's already started.
 | 
						|
                pass
 | 
						|
 | 
						|
        msg = self.token + msg
 | 
						|
        try:
 | 
						|
            self._thread._queue.put_nowait(msg)
 | 
						|
        except Exception:
 | 
						|
            # Queue is full, try to remove the oldest message and put again
 | 
						|
            try:
 | 
						|
                self._thread._queue.get_nowait()
 | 
						|
                self._thread._queue.put_nowait(msg)
 | 
						|
            except Exception:
 | 
						|
                # Race condition, no need for any action here
 | 
						|
                pass
 | 
						|
 | 
						|
    def emit(self, record):
 | 
						|
        msg = self.format(record).rstrip('\n')
 | 
						|
        self.emit_raw(msg)
 | 
						|
 | 
						|
    def close(self):
 | 
						|
        logging.Handler.close(self)
 | 
						|
 |