dgilman/gspread_asyncio

Extra logging handler

tesence opened this issue · 1 comments

I'm using gspread_asyncio 1.9.0 (with gspread 5.10.0) and I've been noticing a weird logging behaviour.

Whenever AsyncioGspreadClient.open() is called, an extra handler is added to the one I'm configure in my code, dupplicating any log output

Small example that works as expected with vanilla gspread 5.10.0

import logging

import gspread
from google.oauth2.service_account import Credentials

def main():
    
    steam_handler = logging.StreamHandler()
    logger = logging.getLogger("test")
    logger.setLevel(logging.INFO)
    logger.addHandler(steam_handler)
    
    creds = Credentials.from_service_account_file(
        "service_account.json",
        scopes = [
            "https://spreadsheets.google.com/feeds",
            "https://www.googleapis.com/auth/spreadsheets",
            "https://www.googleapis.com/auth/drive",
        ]
    )
    client = gspread.authorize(creds)
    logger.info("Before client.open")
    spreadsheet = client.open(title="My Spreadsheet")
    logger.info('After client.open')

if __name__ == "__main__":
    main()

Output

Before client.open
After client.open

The same example with gspread_asyncio 1.9.0 (relying on gspread 5.10.0)

import asyncio

import logging

import gspread_asyncio
from google.oauth2.service_account import Credentials

async def main():
    
    steam_handler = logging.StreamHandler()
    logger = logging.getLogger("test")
    logger.setLevel(logging.INFO)
    logger.addHandler(steam_handler)

    def get_creds():
        creds = Credentials.from_service_account_file("service_account.json")
        scoped = creds.with_scopes([
            "https://spreadsheets.google.com/feeds",
            "https://www.googleapis.com/auth/spreadsheets",
            "https://www.googleapis.com/auth/drive",
        ])
        return scoped

    agcm = gspread_asyncio.AsyncioGspreadClientManager(get_creds)
    
    agc = await agcm.authorize()
    
    logger.info("Before agc.open")
    spreadsheet = await agc.open(title="My Spreadsheet")
    logger.info('After agc.open')

if __name__ == "__main__":
    asyncio.run(main())

Output

Before agc.open
After agc.open
INFO:test:After agc.open

The way the library did logging made sense when I put the thing together but may not be the best way to do it today. I apologize if this is a huge inconvenience for you, if I had the time I'd redo it but right now it's not worth messing with.

To answer your question, though: the library calls the module-level logging.debug, logging.error methods as part of its operation. It does not create its own logger (e.g. logger = logging.getLogger(__name__) like you might expect. This could be the cause of your problems.

To get around this, if you subclass AsyncioGspreadClientManager you can re-implement before_gspread_call, handle_gspread_error and handle_requests_error to do what you need it to do w/r/t logging. The original implementations are 1-2 lines long so it's hopefully not a huge lift.

I am not sure what your goals are with logging but maybe it could be something like this, or you could pass a logger instance in directly:

class MyAsyncioGspreadClientManager(AsyncioGspreadClientManager):
    def __init__(self, *args, **kwargs):
        self.logger = logging.getLogger(kwargs.pop("logger_name"))
        super().__init__(*args, **kwargs)

    async def before_gspread_call(self, method, args, kwargs):
        self.logger.debug(
            "Calling {0} {1} {2}".format(method.__name__, str(args), str(kwargs))
        )

    async def handle_gspread_error(self, e, method, args, kwargs):
        self.logger.error(
            "Error while calling {0} {1} {2}. Sleeping for {3} seconds.".format(
                method.__name__, str(args), str(kwargs), self.gspread_delay
            )
        )
        # Wait a little bit just to keep from pounding Google
        await asyncio.sleep(self.gspread_delay)

    async def handle_requests_error(self, e, method, args, kwargs):
        # By default, retry forever.
        self.logger.error(
            "Error while calling {0} {1} {2}. Sleeping for {3} seconds.".format(
                method.__name__, str(args), str(kwargs), self.gspread_delay
            )
        )
        # Wait a little bit just to keep from pounding Google
        await asyncio.sleep(self.gspread_delay)