Logging: Verewigt sich nicht im Log

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
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Ich hab hier ein Stück Code bei dem Logging wider erwarten nicht funktioniert. Das was ich beobachten konnte, "missing package" schreibt er recht zuverlässig rein, alles andere gar nicht. Direkt im Interpreter scheints zu funktionieren und shutdown brachte keine Besserung. Jemand eine Idee woran es liegen könnte?

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function
import networkx as nx
import matplotlib.pyplot as plt
from os import getcwd, walk
from os.path import join, exists, split
from sys import argv
import json
import logging

def collects_packages_path(start_dir):
    '''collect all paths for better runtime performance'''
    collector = {}
    for current_dir, dirs, files in walk(start_dir):
        #skip git directories
        if ".git" in dirs:
            dirs.remove(".git")
        #O(1) instead of O(n) for looking in files
        full_path = join(current_dir, "Android.mk")
        if exists(full_path):
            collector[split(current_dir)[-1]] = full_path
    return collector

def get_dependency(file_name):
    ''''extract written requirements from Android.mk'''
    depend = []
    with open(file_name) as make_file:
        for line in make_file:
            line = line.strip()
            if line.startswith("#") or not line:
                continue
            prefix = line.split()[0]
            if prefix in ("PRODUCT_PACKAGES"):#, "LOCAL_SHARED_LIBRARIES"):#, "LOCAL_JAVA_LIBRARIES"):
                while True:
                    line = make_file.next().strip()
                    if line: 
                        depend.append(line.split("\\")[0].strip())
                    else:
                        break
            elif prefix in ("LOCAL_REQUIRED_MODULES"):#, "LOCAL_WHOLE_STATIC_LIBRARIES"):
                line = make_file.next().strip()
                #element right from ":=", separated from " " and trimmed spaces
                depend.extend(map(lambda x: x.strip(), line.split(":=")[-1].split()))
    return depend

def save(package_name, data):
    '''save package dependency to json'''
    file_name = "%s_%04d.json" % (package_name, len(data) - 1)
    with open(file_name, "w") as dump:
        dump.write(json.dumps(data))

def save_img(name, package_dependency):
    '''create graph and store as image'''
    g = nx.Graph()
    logging.info("create %d nodes" % len(package_dependency))
    for node in package_dependency:
        g.add_node(node)
    logging.info("create edges")
    for key in package_dependency:
        for neighbour in package_dependency[key]:
            g.add_edge(key, neighbour)
    file_name = "%s.png" % name
    logging.info("save image %s" % file_name)
    nx.draw(g)
    plt.savefig(file_name)

def main():
    if argv[1:]:
        package_dependency = {}
        package_name = argv[1]
        start_dir = getcwd()
        logging.basicConfig(filename="depend.log")
        logging.info("collect package paths")
        dirs = collects_packages_path(start_dir)
        logging.info("collect recursive dependencies")
        package_path = package_name
        if package_name.endswith(".mk"):
            #relative to android sdk root directory 
            package_path = join("build/target/product", package_name)
        if exists(package_path):
            dirs[package_name] = package_path
        if package_name in dirs:
            logging.info("module %s found" % package_name)
            queue = get_dependency(dirs[package_name])
            package_dependency[package_name] = queue[:]
            while queue:
                if queue[0] not in package_dependency:
                    logging.info("module %s found" % queue[0])
                    dependencies = get_dependency(dirs[queue[0]])
                    package_dependency[queue[0]] = dependencies
                    keys = set(package_dependency.keys())
                    new_entries = set(dependencies).difference(set(package_dependency.keys()))
                    logging.info("found for node %s new dependencies %s" % (
                                    queue[0], new_entries))
                    queue.extend(new_entries)
                    
                queue.pop(0)
            logging.info("save structure as json file")
            save(package_name, package_dependency)
            logging.info("create image from dependency graph")
            save_img(package_name, package_dependency)
        else:
            logging.error("package name not recognised")
    else:
        logging.error("missing package name")
 
if __name__ == "__main__":
    main()
    

„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
BlackJack

@darktrym: Und wenn Du das Logging-Level auf `logging.INFO` setzt?
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Danke das wars, das richtige Log Level setzen.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
BlackJack

@darktrym: Noch ein paar Anmerkungen:

Die ”Tupel” für die ``in``-Tests in `get_dependency()` sind keine Tupel, deshalb funktionieren die ``if``\s nicht wie gewünscht beziehungsweise funktioniert es nur zufällig. Zum Tupel mit einem Element fehlt noch ein Komma:

Code: Alles auswählen

In [11]: 'PRODUCT_PACKAGES' in ('PRODUCT_PACKAGES')
Out[11]: True

In [12]: # aber auch:

In [13]: 'PACKAGES' in ('PRODUCT_PACKAGES')
Out[13]: True

In [14]: # richtig:

In [15]: 'PRODUCT_PACKAGES' in ('PRODUCT_PACKAGES',)
Out[15]: True

In [16]: 'PACKAGES' in ('PRODUCT_PACKAGES',)
Out[16]: False
Wobei der Test mit nur einem Element natürlich sowieso unnötig umständlich ist, wenn also die auskommentierten Teile der Zeilen dauerhaft weg sollen, dann besser mit ``==`` direkt gegen den Wert vergleichen.

Beim schreiben der JSON-Datei würde ich gleich `json.dump()` verwenden.

Beim Loggen kann man die Formatzeichenkette und die Werte die dort eingefügt werden, trennen. So spart man sich zur Laufzeit die Zeichenkettenformatierung wenn das Logging-Level überhaupt nicht aktiv ist.

In `save_image()` könnte man in der äusseren Schleife gleich über die Schlüssel/Wert-Paare iterieren:

Code: Alles auswählen

    for key, neighbours in package_dependencies.viewitems():
        for neighbour in neighbours:
            graph.add_edge(key, neighbour)
``if argv[1:]`` als Test ob da Argumente vorhanden sind ist IMHO ungewöhnlich. An der Stelle könnte man auch das `argparse`-Modul verwenden.

Ich denke an fast allen, wenn nicht sogar an allen Stellen, wo `dependency` als Teil eines Namens verwendet wurde, sollte eigentlich `dependencies` stehen.

Wenn man bei `os.path.join()` dann doch wieder hart kodierte zusammengesetzte Teilpfade verwendet, ist es nicht mehr plattformunabhängig. Also statt so etwas wie ``os.path.join('a/b', c)`` sollte man ``os.path.join('a', 'b', c)`` schreiben.

Statt immer wieder ``queue[0]`` zu schreiben hätte man einmal am Anfang der Schleife das erste Element aus der Queue `pop()`\en und an einen Namen binden können. Und statt einer Liste hätte eine `collections.deque` ein besseres Laufzeitverhalten bei dem `pop()` ”von links”.

`keys` wird in der Funktion zwar an einen Wert gebunden, der wird dann aber nirgends verwendet.

`new_entries` liesse sich kürzer ausdrücken. Das Argument von `difference()` muss kein `set` sein, ein iterierbares Objekt tut es auch, und `a_dict.keys()` ist in dem Kontext das selbe wie `a_dict` selbst: ein iterierbares Objekt über die Schlüssel.

Die `main()`-Funktion könnte dann so aussehen (ungetestet):

Code: Alles auswählen

def main():
    logging.basicConfig(filename='depend.log', level=logging.INFO)
    try:
        package_name = argv[1]
    except IndexError:
        logging.error('missing package name')
    else:
        logging.info('collect package paths')
        dirs = collects_packages_path(getcwd())
        logging.info('collect recursive dependencies')
        package_path = package_name
        if package_name.endswith('.mk'):
            # 
            # Relative to android sdk root directory.
            # 
            package_path = join('build', 'target', 'product', package_name)
        
        if exists(package_path):
            dirs[package_name] = package_path
        
        try:
            queue = deque(get_dependency(dirs[package_name]))
        except KeyError:
            logging.error('package name not recognised')
        else:
            logging.info('module %s found', package_name)
            package_dependencies = {package_name: list(queue)}
            while queue:
                path = queue.popleft()
                if path not in package_dependencies:
                    logging.info('module %s found', path)
                    dependencies = get_dependency(dirs[path])
                    package_dependencies[path] = dependencies
                    new_entries = set(dependencies).difference(
                        package_dependencies
                    )
                    logging.info(
                        'found for node %s new dependencies %s',
                        path,
                        new_entries
                    )
                    queue.extend(new_entries)
            logging.info('save structure as json file')
            save(package_name, package_dependencies)
            logging.info('create image from dependency graph')
            save_image(package_name, package_dependencies)
Ist für meinen Geschmack ein bisschen zu umfangreich. Ich hätte das erstellen von `package_dependencies` wohl auch noch in eine eigene Funktion ausgelagert.
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Ich danke dir nochmals, der Fehler ist mir nicht aufgefallen.

Der Rest, der Code ist WIP da wackelts noch an vielen Stellen.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Antworten