J'ai un programme Python Spark que je lance avec spark-submit
. Je veux y mettre des déclarations de journalisation.
logging.info("This is an informative message.")
logging.debug("This is a debug message.")
Je souhaite utiliser le même enregistreur que Spark utilise pour que les messages de journalisation soient au même format et que le niveau soit contrôlé par les mêmes fichiers de configuration. Comment puis-je faire cela?
J'ai essayé de mettre les instructions logging
dans le code et de commencer avec un logging.getLogger()
. Dans les deux cas, je vois les messages du journal de Spark mais pas les miens. J'ai consulté la documentation de journalisation Python , mais je n'ai pas été en mesure de le comprendre à partir de là.
Je ne sais pas s'il s'agit de quelque chose de spécifique aux scripts soumis à Spark ou si je ne comprends pas comment fonctionne la journalisation.
Vous pouvez obtenir le consignateur à partir de l'objet SparkContext:
log4jLogger = sc._jvm.org.Apache.log4j
LOGGER = log4jLogger.LogManager.getLogger(__name__)
LOGGER.info("pyspark script logger initialized")
Vous devez obtenir le consignateur pour spark lui-même. Par défaut, getLogger () le renverra pour votre propre module. Essayez quelque chose comme:
logger = logging.getLogger('py4j')
logger.info("My test info statement")
Il pourrait également s'agir de "pyspark" au lieu de "py4j".
Si la fonction que vous utilisez dans votre programme d'allumage (et qui effectue une certaine journalisation) est définie dans le même module que la fonction principale, cela provoquera une erreur de sérialisation.
Ceci est expliqué ici et un exemple de la même personne est donné ici
J'ai aussi testé cela sur spark 1.3.1
MODIFIER:
Pour changer la journalisation de STDERR à STDOUT, vous devez supprimer le StreamHandler actuel et en ajouter un nouveau.
Trouver le gestionnaire de flux existant (cette ligne peut être supprimée lorsque vous avez terminé)
print(logger.handlers)
# will look like [<logging.StreamHandler object at 0x7fd8f4b00208>]
Il n'y en aura probablement qu'un seul, mais sinon, vous devrez mettre à jour votre position.
logger.removeHandler(logger.handlers[0])
Ajouter un nouveau gestionnaire pour sys.stdout
import sys # Put at top if not already there
sh = logging.StreamHandler(sys.stdout)
sh.setLevel(logging.DEBUG)
logger.addHandler(sh)
La clé d'interaction de pyspark et de Java log4j est le jvm . Ce qui suit est du code python, la conf manque l'URL, mais il s'agit de la journalisation.
from pyspark.conf import SparkConf
from pyspark.sql import SparkSession
my_jars = os.environ.get("SPARK_HOME")
myconf = SparkConf()
myconf.setMaster("local").setAppName("DB2_Test")
myconf.set("spark.jars","%s/jars/log4j-1.2.17.jar" % my_jars)
spark = SparkSession\
.builder\
.appName("DB2_Test")\
.config(conf = myconf) \
.getOrCreate()
Logger= spark._jvm.org.Apache.log4j.Logger
mylogger = Logger.getLogger(__name__)
mylogger.error("some error trace")
mylogger.info("some info trace")
Nous devions nous connecter depuis les exécuteurs , pas depuis le nœud du pilote. Nous avons donc fait ce qui suit:
Nous avons créé un /etc/rsyslog.d/spark.conf
sur tous les nœuds (à l'aide d'une méthode Bootstrap avec des messages Amazon Elastic Map Reduceso that the Core nodes forwarded syslog
local1` au nœud maître.
Sur le nœud maître, nous avons activé les écouteurs UDP et TCP syslog, et nous l'avons configuré pour que tous les messages local
soient consignés dans /var/log/local1.log
.
Nous avons créé un consignateur Syslog de module logging
Python dans notre fonction de carte.
Nous pouvons maintenant nous connecter avec logging.info()
. ...
Une des choses que nous avons découvertes est que la même partition est traitée simultanément sur plusieurs exécuteurs. Apparemment, Spark le fait tout le temps, quand il dispose de ressources supplémentaires. Cela gère le cas lorsqu'un exécutant est mystérieusement retardé ou échoue.
La connexion aux fonctions map
nous a beaucoup appris sur le fonctionnement de Spark.
Dans mon cas, je suis simplement heureux d’obtenir mes messages de journal ajoutés à la liste des employés, ainsi que les messages habituels du journal d’étincelles.
Si cela vous convient, l’astuce consiste à rediriger le consignateur Python vers stderr
.
Par exemple, ce qui suit, inspiré de cette réponse , me convient parfaitement:
def getlogger(name, level=logging.INFO):
import logging
import sys
logger = logging.getLogger(name)
logger.setLevel(level)
if logger.handlers:
# or else, as I found out, we keep adding handlers and duplicate messages
pass
else:
ch = logging.StreamHandler(sys.stderr)
ch.setLevel(level)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
return logger
Usage:
def tst_log():
logger = getlogger('my-worker')
logger.debug('a')
logger.info('b')
logger.warning('c')
logger.error('d')
logger.critical('e')
...
Sortie (plus quelques lignes pour le contexte):
17/05/03 03:25:32 INFO MemoryStore: Block broadcast_24 stored as values in memory (estimated size 5.8 KB, free 319.2 MB)
2017-05-03 03:25:32,849 - my-worker - INFO - b
2017-05-03 03:25:32,849 - my-worker - WARNING - c
2017-05-03 03:25:32,849 - my-worker - ERROR - d
2017-05-03 03:25:32,849 - my-worker - CRITICAL - e
17/05/03 03:25:32 INFO PythonRunner: Times: total = 2, boot = -40969, init = 40971, finish = 0
17/05/03 03:25:32 INFO Executor: Finished task 7.0 in stage 20.0 (TID 213). 2109 bytes result sent to driver