Skip to main content

Exelog: Meticulous logging for Apache Spark

Exelog: Meticulous logging for Apache Spark

Enabling meticulous logging for Spark Applications

Exelog is a refactored logging module that provides a decorator based approach to ensure standard Python logging from PySpark Executor nodes also.

Why?

The problem: logging from Spark executors doesn't work with normal logging

In Apache Spark, the actual data processing is done in what's called "executors", which are separate processes that are separate from the "driver" program. For end users, full control is only over the driver process, but not so much over the executor processes.

For example, in the PySpark driver program we can set up standard python logging as desired, but this setup is not replicated in the executor processes. There are no out-of-the-box logging handlers for executors, so all logging messages from executors are lost. Since Python 3.2 however, when there are no handlers, there is still a "improvised" handler that will show warning() and error() messages in their bare format on standard error, but for proper logging we probably need something more flexible and powerful than that.

Illustration in interactive PySpark shell:

>>> import os, logging
>>> logging.basicConfig(level=logging.INFO)

>>> def describe():
...     return "pid: {p}, root handlers: {h}".format(p=os.getpid(), h=logging.root.handlers)
... 
>>> describe()
'pid: 8915, root handlers: [<StreamHandler <stderr> (NOTSET)>]'

>>> sc.parallelize([4, 1, 7]).map(lambda x: describe()).collect()
['pid: 9111, root handlers: []', 'pid: 9128, root handlers: []', 'pid: 9142, root handlers: []']

The initial describe() happens in the driver and has root handlers because of the basicConfig() beforehand. However, the describe() calls in the map() happen in separate executor processes (note the different PIDs) and got no root handlers.

Exelog: Logging Spark executor execution one decorator at a time

Various ways for setting up logging for executors may be found on the internet. It usually entails sending and loading a separate file containing logging configuration code. Depending on the use case, managing this file may be difficult. One of the approaches is here

In contrast, Exelog takes a decorator based approach. We just have to decorate the data processing functions which we are passing to map(), filter(), sortBy(), etc.

A very minimal example:

@exelog.enable_info_logging
def process(x):
    logger.info("Got {x}".format(x=x))
    return x * x

result = rdd.map(process).collect()

What will happen here is that the first time process() is called in the executor, basic logging is set up with INFO level, so that logging messages are not lost.

Options and finetuning

The enable_exelog decorator will do a basic logging setup using logging.basicConfig(), and desired options can be directly provided to the decorator as illustrated in the following example using the interactive PySpark shell:

>>> import logging
>>> from exelog import enable_exelog
>>> logger = logging.getLogger("example")

>>> @enable_exelog(level=logging.INFO)
... def process(x):
...     logger.info("Got {x}".format(x=x))
...     return x * x
... 
>>> sc.parallelize(range(5)).map(process).collect()
INFO:example:Got 0
INFO:example:Got 1
INFO:example:Got 3
INFO:example:Got 2
INFO:example:Got 4
[0, 1, 4, 9, 16]

To improve readability or code reuse, you can of course predefine decorators:

with_logging = enable_exelog(
    level=logging.INFO,
    format="[%(process)s/%(name)s] %(levelname)s %(message)s"
)

@with_logging
def process(x):
    ...

exelog also defines some simple predefined decorators:

# Predefined decorator for stderr/NOTSET logging
enable_notset_logging = enable_exelog(level=logging.NOTSET)

# Predefined decorator for stderr/DEBUG logging
enable_debug_logging = enable_exelog(level=logging.DEBUG)

# Predefined decorator for stderr/INFO logging
enable_info_logging = enable_exelog(level=logging.INFO)

# Predefined decorator for stderr/WARN logging
enable_warn_logging = enable_exelog(level=logging.WARN)

# Predefined decorator for stderr/ERROR logging
enable_error_logging = enable_exelog(level=logging.ERROR)

# Predefined decorator for stderr/CRITICAL logging
enable_critical_logging = enable_exelog(level=logging.CRITICAL)

Fine-grained logging set up

If the logging.basicConfig() API is not flexible enough for your desired setup, you can also inject more advanced setup code with the initialized_call decorator. This decorator is not limited to logging setup, it just expects a callable (that can be called without arguments). A very simple example:

@exelog.initialized_call(lambda: print("Executor logging enabled"))
def process(x):
    ....

This will print "Executor logging enabled" the first time the process function is called in each executor.

Metadata

Release files for exelog 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for exelog 0.0.1
File Size Uploaded Provenance
exelog-0.0.1.tar.gz 8.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for exelog 0.0.1
File Interpreter ABI Platform
exelog-0.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 17.1 kB

Release files / exelog-0.0.1.tar.gz

Download URL exelog-0.0.1.tar.gz
Size 8.4 kB
Tags Source
SHA-256 checksum
How to use checksums
1a17d9532e6d7533be169a3e6f36b64cd574b8d995e26566e51c27728de2b85a
BLAKE2b-256 checksum
How to use checksums
e9c538f4b468be3508abb7c095a0132a8edb3225f6a43da620c84c0ac543f578
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.6.0 importlib_metadata/4.8.2 pkginfo/1.8.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.10.0

Release files / exelog-0.0.1-py3-none-any.whl

Download URL exelog-0.0.1-py3-none-any.whl
Size 8.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c67039d194698abafa6f16c3205684d15f3b3e5183d932e2722a50f5bdccda70
BLAKE2b-256 checksum
How to use checksums
6a2e0e405791da54a602d0a40f22fc7b848aa0bd57a28435925b4ac83a3751db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.6.0 importlib_metadata/4.8.2 pkginfo/1.8.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.10.0

Security for exelog 0.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page