Problem bei logging in Klasse

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
sls
User
Beiträge: 480
Registriert: Mittwoch 13. Mai 2015, 23:52
Wohnort: Country country = new Zealand();

Mit folgendem Skript möchte ich für die FQDN bei einem Dyndns-Provider die externe IP-Adresse täglich updaten. Das hat bis dato auch funktioniert, beim Implementieren eines loggings kommen nun jedoch Fehler, mit denen ich nichts anfangen kann:

Code: Alles auswählen

import base64
import logging.config
import subprocess
import sys
import yaml
import requests


class NoIPUpdater:
    def __init__(self, hostname, username, password, update_url, user_agent, logging_config):
        self.hostname = hostname
        self.username = username
        self.password = password
        self.update_url = update_url
        self.user_agent = user_agent
        self.external_ip = self.get_external_ip()
        self.headers = {'User-Agent': self.user_agent, 'From': self.username}
        self.logging_config = logging_config
        logging.config.dictConfig(self.logging_config)
        self.logger = logging.getLogger(__name__)
        self.logger.info("NoIP updater started...")

    def get_external_ip(self):
        try:
            fetch_external_ip = subprocess.check_output(['curl', 'ifconfig.me'])
            external_ip = fetch_external_ip.decode().replace('\n', '')
            self.logger.info("Fetched external IP {}".format(external_ip))
        except Exception:
            self.logger.error("Fetching external IP failed!")
        return external_ip

    def update_external_ip(self):
        try:
            auth = "{}:{}".format(self.username, self.password)
            auth_encoded = base64.encodebytes(auth.encode('utf-8'))
            update_url = self.update_url.format(auth_encoded, self.hostname, self.external_ip)
            response = requests.get(update_url, headers=self.headers)
            self.logger.info("{} has been updated with IP {}".format(self.hostname, self.external_ip))
        except Exception:
            self.logger.error("Update hostname failed!")


if __name__ == '__main__':
    with open(sys.argv[1], 'r') as fd:
        config = yaml.load(fd)

    update_public_ip = NoIPUpdater(config['noip']['hostname'], config['noip']['username'], config['noip']['password'],
                                   config['noip']['noip_update_url'], config['noip']['user_agent'], config['logging'])
    update_public_ip.update_external_ip()
Traceback:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 15 0 15 0 0 9 0 --:--:-- 0:00:01 --:--:-- 9
Traceback (most recent call last):
File "/home/sls/IdeaProjects/automate/noiptest.py", line 28, in get_external_ip
self.logger.info("Fetched external IP {}".format(external_ip))
AttributeError: 'NoIPUpdater' object has no attribute 'logger'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/home/sls/IdeaProjects/automate/noiptest.py", line 49, in <module>
config['noip']['noip_update_url'], config['noip']['user_agent'], config['logging'])
File "/home/sls/IdeaProjects/automate/noiptest.py", line 16, in __init__
self.external_ip = self.get_external_ip()
File "/home/sls/IdeaProjects/automate/noiptest.py", line 30, in get_external_ip
self.logger.error("Fetching external IP failed!")
AttributeError: 'NoIPUpdater' object has no attribute 'logger'
Das exception-Handling und den hässlichen subprocess-call wollte ich später beseitigen, mir geht's wirklich erstmal nur um das Logging-Problem - bei anderen Applikationen hat das *so* immer problemlos funktioniert, auch wenn ich nicht weiß ob *so* richtig ist.
When we say computer, we mean the electronic computer.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@sls: der Fehler ist doch eindeutig. Den logger erzeugt man normalerweise auf Modulebene. `get_external_ip` braucht kein self (außer dem logger, der nicht an self gebunden sein sollte), ist also keine Methode. Statt replace würde ich strip benutzen. Warum benutzt Du curl, wenn Du doch schon requests eingebunden hast?? Das Fehlerhandling ist fehlerhaft, denn im Fehlerfall ist `external_ip` nicht gesetzt.
Die Klasse ist jetzt keine wirkliche Klasse mehr, denn sie besteht nur noch aus einer Methode `update_external_ip`. Die Klasse kann also durch eine Funktion ersetzt werden.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Im Falle einer Ausnahme würde ich ja auch `logging.exception()` verwenden, damit man nicht nur nachlesen kann *das* etwas nicht funktioniert hat, sondern auch *wo* und *warum*. Das ist bei der Fehlersuche manchmal sehr hilfreich. :-)
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@sls: Beim Logging sollte man nicht selbst werte in die Zeichenkette formatieren, sondern %-Platzhalter verwenden und die Werte als zusätzliche Argumente übergeben. Das ist effizienter wenn das Loglevel so eingestellt ist, das die Nachricht gar nicht ausgegeben werden muss/soll.

`fetch_external_ip` wäre ein Name für eine Funktion oder Methode die das tut, aber nicht für die Ausgabe eines externen Programms.

Die Antwort von `requests` wird nicht geprüft, das heisst es kann passieren das es nicht funktioniert, dem Benutzer aber trotzdem mitgeteilt wird es hätte funktioniert.

`fd` ist kein guter Name für eine Datei. Das ist aus einer anderen Programmiersprache in der es etwas leicht anderes bedeutet.

Ungetestet:

Code: Alles auswählen

import base64
import logging.config
import sys

import requests
import yaml

LOG = logging.getLogger(__name__)


def get_external_ip():
    response = requests.get(
        'http://ifconfig.me', headers={'User-Agent': 'curl/7.22.0'}
    )
    if response.ok:
        external_ip = response.text.strip()
        LOG.info('Fetched external IP %s', external_ip)
        return external_ip
    else:
        response.raise_for_status()


def update_external_ip(
    hostname, username, password, update_url, user_agent, external_ip
):
    auth_encoded = base64.encodebytes(
        '{}:{}'.format(username, password).encode('utf-8')
    )
    response = requests.get(
        update_url.format(auth_encoded, hostname, external_ip),
        headers={'User-Agent': user_agent, 'From': username},
    )
    if response.ok:
        LOG.info('%s has been updated with IP %s', hostname, external_ip)
    else:
        response.raise_for_status()


def main():
    with open(sys.argv[1], 'r') as config_file:
        config = yaml.load(config_file)

    logging.config.dictConfig(config['logging'])

    LOG.info('NoIP updater started...')
    try:
        external_ip = get_external_ip()
    except Exception:
        LOG.exception('Fetching external IP failed.')
    else:
        noip_config = config['noip']
        try:
            update_external_ip(
                noip_config['hostname'],
                noip_config['username'],
                noip_config['password'],
                noip_config['noip_update_url'],
                noip_config['user_agent'],
                external_ip,
            )
        except Exception:
            LOG.exception('Update hostname failed!')


if __name__ == '__main__':
    main()
`update_external_ip()` bekommt für meinen Geschmack zu viele Argumente. Da würde ich schauen ob man die Update-URL, Benutzer, und Passwort nicht als eine URL übergeben kann. Bei der Update-URL scheint mir auch Wissen über den Dienst kodiert zu sein, das ich eher in der Funktion kodieren würde.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
sls
User
Beiträge: 480
Registriert: Mittwoch 13. Mai 2015, 23:52
Wohnort: Country country = new Zealand();

@Sirius3: mein erster Ansatz war tatsächlich das ohne Klasse zu bauen, ich dachte mir nur dass ich später mehrere Methoden implementiere. Vermutlich ist die Entscheidung aber wohl eher gefallen, weil ich als Java-Entwickler sowieso alles in Klassen schreibe.

Wie ist das eigentlich, wenn ich eine Variablendeklaration innerhalb eines try-except-Blocks vornehme? Gehört die Variable in Python auch vorher schon initialisiert?

@__blackjack__: erstmal danke für die Energie und unglaublich wertvollen Tipps, das meiste hier "dachte ich mir schon vorher irgendwie", bin aber noch nicht so sicher dass ich das im Code umsetzen kann.

Ich habe das mal so übernommen und die Variablen von `noip_update_url` und `user_agent` "gehardcoded", da sich diese nicht ändern bzw. erster mit format sowieso zusammengeschraubt wird.

Was ich noch nicht wirklich verstanden habe: wenn ich einen Logger auf Modulebene erzeugen sollte, wie verwende ich diesen dann innerhalb einer Klasse? Oder hält logging da noch was anderes tolles bereit?

EDIT: so wie ich das aus der Doku jetzt entnehme, müsste ich den Logger auf Modulebene erzeugen (z.B: LOG = logging.getLogger('app')) und kann innerhalb der Klasse z.B. via `self.log = logging.getLogger('app.Klasse')` auf selbigen zugreifen, da dieser dann nicht erneut auf Klassenebene "erzeugt" wird, richtig?
When we say computer, we mean the electronic computer.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

So wie alles andere, das auf Modulebene lebt und das du auch referenzierst. Zb importierte Namen, Module oder definierte Funktionen. Wenn du

logger = logging.getLogger(...)

schreibst, kannst du in Methoden einfach

Code: Alles auswählen

def foo(self):
    logger.info(...)
schreiben. So wie auch os.path.join und alles andere, das du so benutzt. Es gibt also keinen Grund, sich die klasse mit extra Feldern zuzumüllen.
Benutzeravatar
sls
User
Beiträge: 480
Registriert: Mittwoch 13. Mai 2015, 23:52
Wohnort: Country country = new Zealand();

Das ergibt sinn.Ich habe das Skript von __blackjack__ weitestgehend übernommen, mir ist jetzt nur aufgefallen dass nichts mehr in ein Logfile geschrieben wird (hatte bis gestern Abend noch wunderbar funktioniert) - der Logger scheint seinen Dienst quittiert zu haben und ich kann mir nicht wirklich erklären warum, ich habe da nichts mehr angefasst. Ich verwende Intellij und habe für das Projekt ein virtualenv eingerichtet.

EDIT: hat sich erledigt, mir ist ein kleiner Fauxpas unterlaufen. Die History hat's erklärt.
When we say computer, we mean the electronic computer.
Antworten