Search code examples
pythoncgstreamerpygobjectnvidia-deepstream

Setting GStreamer custom logging from Python triggers Segmentation fault


Notes:

  • That there's already an (almost) identical question, but I wanted to give more context (and marked that one as a duplicate)

  • I'll be comparing log levels (probably using terms like: lower, higher, smaller, greater, decrease, increase, <, >, ...). That applies to their verbosity (amount of produced output): ERROR < WARNING < ... < INFO < ... < TRACE < ..., and NOT to their criticality (impact)

Working on a task to make logging on a complex GStreamer application consistent / uniform. One part is having GStreamer elements logs (generated by GST_* macros (expanding to GST_CAT_LEVEL_LOG, which in turn expands to a gst_debug_log call)) in Python.
That should be possible using [FreeDesktop.GStreamer]: GstInfo - Gst.debug_add_log_function (noticed the G_GNUC_NO_INSTRUMENT remark, but don't think there's anything that can be done at Python level).

Things generally work fine, although I fear that if left to run for long times (and higher log levels) some problems (memory consumption increase, even SegFaults, ...) may arise (but I'm not there yet).
But some elements run into serious problems.

Environment

Nvidia's DeepStream 6.2 container (Ubuntu 20.04.5 LTS - debugged mostly on Triton flavor, but reproducible on the others):

  • GStreamer 1.16.3

  • Python 3.8.10

  • PyGObject 3.36.0

  • NVV4L2 - version seems a bit off:

    gst-inspect-1.0 video4linux2 | grep ersion
      Version                  1.16.3
    gst-inspect-1.0 nvvideo4linux2 | grep ersion
      Version                  1.14.0
    

Narrowed the problem down to a MVCE: to have a pipeline that is short (both textually and also structurally (e.g.: not using bins - which have a short name but can be complex, containing a bunch of other elements)).

code00.py:

#!/usr/bin/env python

import sys
import time

import gi

gi.require_version("Gst", "1.0")
from gi.repository import GLib, Gst


pipeline_string = "videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0"
#pipeline_string = "filesrc location=\"/opt/nvidia/deepstream/deepstream/samples/streams/sample_1080p_h264.mp4\" ! qtdemux ! h264parse ! nvv4l2decoder ! nvvideoconvert ! xvimagesink sync=0"


def log_function(
        category: Gst.DebugCategory,
        level: Gst.DebugLevel,
        file: str,
        function: str,
        line: int,
        obj: Gst.Object,
        msg: Gst.DebugMessage,
        # user_data: Any = None,
):
    txt = (
        f"{Gst.debug_level_get_name(level)} {category.name} - {file}:{line}:{function}:",
        f"<{obj.get_name()} ({getattr(obj.__class__, '__name__')})>" if obj else "<None> ",
        f"{msg.get()}"
    )
    print("".join(txt))


def run_pipeline():
    pipeline = Gst.parse_launch(pipeline_string)
    loop = GLib.MainLoop()
    bus = pipeline.get_bus()
    #bus.add_signal_watch()
    pipeline.set_state(Gst.State.PLAYING)
    start_time = time.time()
    try:
        loop.run()
    except KeyboardInterrupt:
        print("<Ctrl + C> pressed.")
    except:
        print(f"Funky exception caught: {sys.exc_info()[:2]}")
    finally:
        pipeline.set_state(Gst.State.NULL)
        loop.quit()
        print(f"--- Ran for {time.time() - start_time:.3f} seconds")


def main(*argv):
    custom_log = bool(argv)  # Custom log if any argument passed
    print(f"---\nCustom log function: {custom_log}\nRunning pipeline:\n  gst-launch-1.0 -e {pipeline_string}\n---\n")
    Gst.init(None)
    if custom_log:
        Gst.debug_remove_log_function()
        Gst.debug_add_log_function(log_function)
    run_pipeline()


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

Some may argue that code is not complete (no callback to monitor messages on the bus), but I wanted to keep it simple (and I don't care if when playing a video, at the end pipeline doesn't quit - I interrupt it by pressing Ctrl + C anyway). Just to be explicit, I did try with a complete version, but same results.

Output:

[root@cfati-5510-0:~/Work/Dev/StackExchange/StackOverflow/q077932526]> . ~/sopr.sh
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[064bit prompt]>
[064bit prompt]> ls
code00.py
[064bit prompt]> 
[064bit prompt]> # ---------- DEFAULT LOG ----------
[064bit prompt]> GST_DEBUG=2 python ./code00.py  
Python 3.8.10 (default, Jun 22 2022, 20:18:18) [GCC 9.4.0] 064bit on linux

---
Custom log function: False
Running pipeline:
  gst-launch-1.0 -e videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0
---

0:00:00.038000133 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:sink> Unable to try format: Unknown error -1
0:00:00.038029358 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:2942:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:sink> Could not probe minimum capture size for pixelformat YM12
0:00:00.038046357 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:sink> Unable to try format: Unknown error -1
0:00:00.038061061 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:2948:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:sink> Could not probe maximum capture size for pixelformat YM12
0:00:00.038080317 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:2395:gst_v4l2_object_add_interlace_mode:0x13873f0 Failed to determine interlace mode
0:00:00.038109676 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:sink> Unable to try format: Unknown error -1
0:00:00.038123283 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:2942:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:sink> Could not probe minimum capture size for pixelformat NM12
0:00:00.038134452 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:sink> Unable to try format: Unknown error -1
0:00:00.038148304 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:2948:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:sink> Could not probe maximum capture size for pixelformat NM12
0:00:00.038161000 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:2395:gst_v4l2_object_add_interlace_mode:0x13873f0 Failed to determine interlace mode
0:00:00.038243374 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:src> Unable to try format: Unknown error -1
0:00:00.038262117 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:2942:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:src> Could not probe minimum capture size for pixelformat H264
0:00:00.038276388 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:src> Unable to try format: Unknown error -1
0:00:00.038289047 2604541      0x1385550 WARN                    v4l2 gstv4l2object.c:2948:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:src> Could not probe maximum capture size for pixelformat H264
0:00:00.098620457 2604541      0x107bde0 WARN          v4l2bufferpool gstv4l2bufferpool.c:1082:gst_v4l2_buffer_pool_start:<nvv4l2h264enc0:pool:src> Uncertain or not enough buffers, enabling copy threshold
0:00:00.103610735 2604541      0x1399060 WARN          v4l2bufferpool gstv4l2bufferpool.c:1533:gst_v4l2_buffer_pool_dqbuf:<nvv4l2h264enc0:pool:src> Driver should never set v4l2_buffer.field to ANY
^C<Ctrl + C> pressed.
--- Ran for 4.508 seconds

Done.
[064bit prompt]>
[064bit prompt]> # ---------- CUSTOM LOG ----------
[064bit prompt]> GST_DEBUG=2 python ./code00.py 1
Python 3.8.10 (default, Jun 22 2022, 20:18:18) [GCC 9.4.0] 064bit on linux

---
Custom log function: True
Running pipeline:
  gst-launch-1.0 -e videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0
---

WARN    v4l2 - gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<sink (Pad)>Unable to try format: Unknown error -1
WARN    v4l2 - gstv4l2object.c:2942:gst_v4l2_object_probe_caps_for_format:<sink (Pad)>Could not probe minimum capture size for pixelformat YM12
WARN    v4l2 - gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<sink (Pad)>Unable to try format: Unknown error -1
WARN    v4l2 - gstv4l2object.c:2948:gst_v4l2_object_probe_caps_for_format:<sink (Pad)>Could not probe maximum capture size for pixelformat YM12
code00.py:44: Warning: g_object_is_floating: assertion 'G_IS_OBJECT (object)' failed
  pipeline.set_state(Gst.State.PLAYING)
code00.py:44: Warning: g_object_get_qdata: assertion 'G_IS_OBJECT (object)' failed
  pipeline.set_state(Gst.State.PLAYING)
Segmentation fault (core dumped)

Notes:

  • Error is reproducible for any of the 3 coders in the NVV4L2 plugin

  • Looking at the above differences, the 1st line that appears in the good run after the last one seen in the crashed one is: v4l2 gstv4l2object.c:2395:gst_v4l2_object_add_interlace_mode:0x13873f0 Failed to determine interlace mode (this happens every time)

  • Works fine when decreasing the log level (ERROR, 1), but then it's pretty much useless (log wise)

Based on the observations above, one can conclude that the error has something to do with that particular line. Worth mentioning that I browsed several Gst-Plugins-Good versions (gst-plugins-good/sys/v4l2/gstv4l2object.c) but none of them has the exact line numbers (those particular lines of code don't seem to change though).

Also noted that error occurs even for:

  • Dummy log functions (containing just a pass statement)

  • Extension modules (I created one with a (dummy) log function)

References:

On a closing note, marshaling the GStreamer objects to Python will have a negative impact on performance (especially if happening often during pipeline run).


Solution

  • Note: Posting an answer, although it doesn't identify and fix the problem (not yet, at least).
    It's more like a debugging log (and possible ways to work around it).

    The next logical step (for me) was to try replicating the behavior from C (get rid of Python layer). Due to some comments in the 1st and 2nd references (from the question), indicating a GStreamer bug (the behavior no longer being reproducible with v1.20), I was kind of expecting similar results.

    • logfunctions.h:

      #pragma once
      #include <gst/gst.h>
      
      
      typedef enum {
          Data = 1,
      } LogFunctionInfo;
      
      #if defined(__cplusplus)
      extern "C" {
      #endif
      
      void addLogFunction(LogFunctionInfo info, int removeDefault);
      void clearLogFunction(LogFunctionInfo info, int addDefault);
      
      #if defined(__cplusplus)
      }
      #endif
      
    • logfunctions.c:

      #include <gst/gst.h>
      
      #include "logfunctions.h"
      
      
      static G_GNUC_NO_INSTRUMENT
      void logFunctionData(
          GstDebugCategory *category,
          GstDebugLevel level,
          const gchar *file,
          const gchar *function,
          gint line,
          GObject *object,
          GstDebugMessage *message,
          gpointer user_data
      )
      {
          g_print(
              "%s %s - %s:%d:%s:<%p (%s)> %s\n",
              gst_debug_level_get_name(level), category->name, file, line, function, object,
      #if defined(NO_OBJECT_CHECK)
              G_OBJECT_TYPE_NAME(object),
      #else
              G_IS_OBJECT(object) ? G_OBJECT_TYPE_NAME(object) : "",
      #endif
              gst_debug_message_get(message)
          );
      }
      
      
      static GstLogFunction logFunction(LogFunctionInfo info)
      {
          switch (info) {
              case Data:
                  return logFunctionData;
              default:
                  return NULL;
          }
      }
      
      
      void addLogFunction(LogFunctionInfo info, int removeDefault)
      {
          if (removeDefault) {
              gst_debug_remove_log_function(NULL);
          }
          GstLogFunction logFunc = logFunction(info);
          if (logFunc) {
              gst_debug_add_log_function(logFunc, NULL, NULL);
          }
      }
      
      
      void clearLogFunction(LogFunctionInfo info, int addDefault)
      {
          GstLogFunction logFunc = logFunction(info);
          if (logFunc) {
              gst_debug_remove_log_function(logFunc);
          }
          if (addDefault) {
              gst_debug_add_log_function(NULL, NULL, NULL);
          }
      }
      
    • main00.c:

      #include <stdio.h>
      #include <time.h>
      
      #include <gst/gst.h>
      
      #include "logfunctions.c"  // logFunctionData is required
      
      #define PIPE_FAIL "videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0"
      //#define PIPE_FAIL "filesrc location=\"/opt/nvidia/deepstream/deepstream/samples/streams/sample_1080p_h264.mp4\" ! qtdemux ! h264parse ! nvv4l2decoder ! nvvideoconvert ! xvimagesink sync=0"
      
      
      static /*volatile*/ GstElement *pipeline = NULL;
      
      
      void signalHandler(int sig)
      {
          if (pipeline) {
              gst_element_send_event(pipeline, gst_event_new_eos());
          }
      }
      
      
      int main(int argc, char **argv)
      {
          char pipeline_string[] = PIPE_FAIL;
          g_print("---\nCustom log function: %s\nRunning pipeline:\n  gst-launch-1.0 -e %s\n---\n",
                  (argc > 1) ? "True" : "False", pipeline_string
          );
      
          gst_init(NULL, NULL);
          if (argc > 1) {
              g_print("Adding log function\n");
              gst_debug_remove_log_function(NULL);
              gst_debug_add_log_function(logFunctionData, NULL, NULL);
          }
          GError *error = NULL;
      
          pipeline = gst_parse_launch(pipeline_string, &error);
          if (!pipeline) {
              g_print("Parse error: %s\n", error->message);
              return -3;
          }
      
          struct sigaction act;
          act.sa_handler = signalHandler;
          sigaction(SIGINT, &act, NULL);
      
          clock_t timeStart = clock();
          gst_element_set_state(pipeline, GST_STATE_PLAYING);
          GstBus *bus = gst_element_get_bus(pipeline);
      
          GstMessage *msg = gst_bus_poll(bus, GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
          switch (GST_MESSAGE_TYPE(msg)) {
              case GST_MESSAGE_EOS: {
                  g_print("EOS\n");
                  break;
              }
              case GST_MESSAGE_ERROR: {
                  GError *err = NULL;
                  gchar *dbg = NULL;
      
                  gst_message_parse_error(msg, &err, &dbg);
                  if (err) {
                      g_printerr("ERROR: %s\n", err->message);
                      g_error_free(err);
                  }
                  if (dbg) {
                      g_printerr("[Debug details: %s]\n", dbg);
                      g_free(dbg);
                  }
              }
              default:
                  g_printerr("Unexpected message of type %d", GST_MESSAGE_TYPE(msg));
                  break;
          }
      
          g_print("--- Ran for %.3f seconds\n", ((double)clock() - timeStart) / CLOCKS_PER_SEC);
      
          gst_message_unref(msg);
          gst_element_set_state(pipeline, GST_STATE_NULL);
          gst_object_unref(bus);
          gst_object_unref(pipeline);
          gst_deinit();
          g_print("\nDone.\n\n");
          return 0;
      }
      

    Notes:

    • Pedantic coders might argue about some "unorthodox" things in the code, but again, it's just for simplicity and don't have any (negative) functional effect

    • Code is way too complex for now, but this structuring will come in handy at a later stage

    Output:

    [root@cfati-5510-0:~/Work/Dev/StackExchange/StackOverflow/q077932526]> . ~/sopr.sh
    ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
    
    [064bit prompt]>
    [064bit prompt]> ls
    code00.py  logfunctions.c  logfunctions.h  main00.c
    [064bit prompt]> gcc -Wall -g -O2 -fPIC -DPIC -pthread -I/usr/include/gstreamer-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -Wl,-no-undefined -o ./main00 ./main00.c -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0 
    [064bit prompt]> ls
    code00.py  logfunctions.c  logfunctions.h  main00  main00.c
    [064bit prompt]>
    [064bit prompt]> # ---------- DEFAULT LOG ----------
    [064bit prompt]> GST_DEBUG=2 ./main00  
    ---
    Custom log function: False
    Running pipeline:
      gst-launch-1.0 -e videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0
    ---
    0:00:00.043512822 2624802 0x55bf01c8d810 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:sink> Unable to try format: Unknown error -1
    0:00:00.043537781 2624802 0x55bf01c8d810 WARN                    v4l2 gstv4l2object.c:2942:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:sink> Could not probe minimum capture size for pixelformat YM12
    0:00:00.043547295 2624802 0x55bf01c8d810 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:sink> Unable to try format: Unknown error -1
    0:00:00.043555280 2624802 0x55bf01c8d810 WARN                    v4l2 gstv4l2object.c:2948:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:sink> Could not probe maximum capture size for pixelformat YM12
    0:00:00.043565126 2624802 0x55bf01c8d810 WARN                    v4l2 gstv4l2object.c:2395:gst_v4l2_object_add_interlace_mode:0x55bf01c81df0 Failed to determine interlace mode
    
    # @TODO - cfati: Truncated output
    
    0:00:00.104239660 2624802 0x55bf01930b00 WARN          v4l2bufferpool gstv4l2bufferpool.c:1082:gst_v4l2_buffer_pool_start:<nvv4l2h264enc0:pool:src> Uncertain or not enough buffers, enabling copy threshold
    0:00:00.109120156 2624802 0x55bf01c91980 WARN          v4l2bufferpool gstv4l2bufferpool.c:1533:gst_v4l2_buffer_pool_dqbuf:<nvv4l2h264enc0:pool:src> Driver should never set v4l2_buffer.field to ANY
    ^C0:00:03.420535632 2624802 0x55bf01c91980 WARN           v4l2allocator gstv4l2allocator.c:1510:gst_v4l2_allocator_dqbuf:<nvv4l2h264enc0:pool:src:allocator> V4L2 provided buffer has bytesused 0 which is too small to include >    data_offset 0
    EOS
    --- Ran for 4.103 seconds
    
    Done.
    [064bit prompt]> 
    [064bit prompt]> # ---------- CUSTOM LOG ----------
    [064bit prompt]> GST_DEBUG=2 ./main00 1
    ---
    Custom log function: True
    Running pipeline:
      gst-launch-1.0 -e videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0
    ---
    Adding log function
    WARN    v4l2 - gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<0x5650b754e7f0 (GstPad)> Unable to try format: Unknown error -1
    WARN    v4l2 - gstv4l2object.c:2942:gst_v4l2_object_probe_caps_for_format:<0x5650b754e7f0 (GstPad)> Could not probe minimum capture size for pixelformat YM12
    WARN    v4l2 - gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<0x5650b754e7f0 (GstPad)> Unable to try format: Unknown error -1
    WARN    v4l2 - gstv4l2object.c:2948:gst_v4l2_object_probe_caps_for_format:<0x5650b754e7f0 (GstPad)> Could not probe maximum capture size for pixelformat YM12
    WARN    v4l2 - gstv4l2object.c:2395:gst_v4l2_object_add_interlace_mode:<0x5650b763f800 ((null))> Failed to determine interlace mode
    
    # @TODO - cfati: Truncated output
    
    WARN    v4l2bufferpool - gstv4l2bufferpool.c:1082:gst_v4l2_buffer_pool_start:<0x7f40d0010350 (GstNvV4l2BufferPool)> Uncertain or not enough buffers, enabling copy threshold
    WARN    v4l2bufferpool - gstv4l2bufferpool.c:1533:gst_v4l2_buffer_pool_dqbuf:<0x7f40d0010350 (GstNvV4l2BufferPool)> Driver should never set v4l2_buffer.field to ANY
    ^CEOS
    --- Ran for 3.482 seconds
    
    Done.
    
    

    So, it worked.
    I know it's too soon, and I don't want to point any fingers as there's a lot of info that I'm missing, but I can't help not thinking about PyGObject. A couple of more things that I found about it during investigations:

    • Running a simple pipeline containing a sink bin: videotestsrc ! autovideosink sync=0 (noticed for AutoVideoSink, FPSDisplaySink) and a level higher than FIXME (3) triggers an assertion error (and SegFault or program freezing). This is one of the reasons why I use XVImageSink directly

    • Calling log functions fails before Gst.init (which sucks as things are being logged during initialization). But discovered (by dumb luck) that for newer versions (tried with v3.46.2) this restriction goes away. It was just a coincidence, I tried on another environment and v3.36.0 works. So, this behavior is due to other factors like: environment variables, dependents (maybe GIR (gir1.2-glib-2.0)), ...

    So I'm trying to move away from it. But there's no straightforward way. Thought about a couple of workarounds.

    1. Log from a C .so (.dll)

    Steps:

    1. Create a .dll containing:

      1. A logging (callback) function mirroring the one from Python. Of course, easier to say than done. If it only formats the info and spits it to StdOut, then it's OK, but if it's doing more than that (e.g.: using loggers or other Python stuff) then this is a dead end

      2. Another function adding the previous one to the GStreamer logging system. Optionally, its counterpart

    2. Use the .dll (#1.2.) from Python via CTypes (check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for a common pitfall)

    Now, the (above) code structure starts to make sense.

    code01.py:

    #!/usr/bin/env python
    
    import ctypes as cts
    import sys
    import time
    
    import gi
    
    from code00 import pipeline_string, run_pipeline
    
    gi.require_version("Gst", "1.0")
    from gi.repository import Gst
    
    
    def main(*argv):
        log_func_info = int(argv[0]) if argv and argv[0].isdecimal() else 0
        print(f"---\nCustom log function info: {log_func_info}\nRunning pipeline:\n  gst-launch-1.0 -e {pipeline_string}\n---\n")
    
        if log_func_info:
            log_dll = cts.CDLL("liblogfunctions.so")
            add_log_function = log_dll.addLogFunction
            add_log_function.argtypes = (cts.c_int, cts.c_int)
            add_log_function.restype = None
            # @TODO - cfati: Same thing for clearLogFunction
    
            add_log_function(log_func_info, 1)
    
        Gst.init(None)
        run_pipeline()
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.\n")
        sys.exit(rc)
    

    Output:

    [064bit prompt]> ls
    code00.py  code01.py  logfunctions.c  logfunctions.h  main00  main00.c
    [064bit prompt]> gcc -Wall -g -O2 -fPIC -DPIC -pthread -I/usr/include/gstreamer-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -Wl,-no-undefined -shared -o ./liblogfunctions.so ./logfunctions.c -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0
    [064bit prompt]> ls
    code00.py  code01.py  liblogfunctions.so  logfunctions.c  logfunctions.h  main00  main00.c
    [064bit prompt]> 
    [064bit prompt]> # ---------- DEFAULT LOG ----------
    [064bit prompt]> GST_DEBUG=2 LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:$(pwd)" python ./code01.py
    Python 3.8.10 (default, Jun 22 2022, 20:18:18) [GCC 9.4.0] 064bit on linux
    
    ---
    Custom log function info: 0
    Running pipeline:
      gst-launch-1.0 -e videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0
    ---
    
    0:00:00.034999454 2640153      0x11a8f50 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:sink> Unable to try format: Unknown error -1
    0:00:00.035022095 2640153      0x11a8f50 WARN                    v4l2 gstv4l2object.c:2942:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:sink> Could not probe minimum capture size for pixelformat YM12
    0:00:00.035029968 2640153      0x11a8f50 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:sink> Unable to try format: Unknown error -1
    0:00:00.035036390 2640153      0x11a8f50 WARN                    v4l2 gstv4l2object.c:2948:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:sink> Could not probe maximum capture size for pixelformat YM12
    0:00:00.035045428 2640153      0x11a8f50 WARN                    v4l2 gstv4l2object.c:2395:gst_v4l2_object_add_interlace_mode:0x11aac60 Failed to determine interlace mode
    
    # @TODO - cfati: Truncated output
    
    0:00:00.095513670 2640153       0xea2de0 WARN          v4l2bufferpool gstv4l2bufferpool.c:1082:gst_v4l2_buffer_pool_start:<nvv4l2h264enc0:pool:src> Uncertain or not enough buffers, enabling copy threshold
    0:00:00.100683366 2640153      0x11bc060 WARN          v4l2bufferpool gstv4l2bufferpool.c:1533:gst_v4l2_buffer_pool_dqbuf:<nvv4l2h264enc0:pool:src> Driver should never set v4l2_buffer.field to ANY
    ^C<Ctrl + C> pressed.
    --- Ran for 2.284 seconds
    
    Done.
    
    [064bit prompt]> 
    [064bit prompt]> # ---------- CUSTOM LOG ----------
    [064bit prompt]> GST_DEBUG=2 LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:$(pwd)" python ./code01.py 1
    Python 3.8.10 (default, Jun 22 2022, 20:18:18) [GCC 9.4.0] 064bit on linux
    
    ---
    Custom log function info: 1
    Running pipeline:
      gst-launch-1.0 -e videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0
    ---
    
    WARN    v4l2 - gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<0x1c2a7d0 (GstPad)> Unable to try format: Unknown error -1
    WARN    v4l2 - gstv4l2object.c:2942:gst_v4l2_object_probe_caps_for_format:<0x1c2a7d0 (GstPad)> Could not probe minimum capture size for pixelformat YM12
    WARN    v4l2 - gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<0x1c2a7d0 (GstPad)> Unable to try format: Unknown error -1
    WARN    v4l2 - gstv4l2object.c:2948:gst_v4l2_object_probe_caps_for_format:<0x1c2a7d0 (GstPad)> Could not probe maximum capture size for pixelformat YM12
    WARN    v4l2 - gstv4l2object.c:2395:gst_v4l2_object_add_interlace_mode:<0x1d1b060 ((null))> Failed to determine interlace mode
    
    # @TODO - cfati: Truncated output
    
    WARN    v4l2bufferpool - gstv4l2bufferpool.c:1082:gst_v4l2_buffer_pool_start:<0x7f7700010090 (GstNvV4l2BufferPool)> Uncertain or not enough buffers, enabling copy threshold
    WARN    v4l2bufferpool - gstv4l2bufferpool.c:1533:gst_v4l2_buffer_pool_dqbuf:<0x7f7700010090 (GstNvV4l2BufferPool)> Driver should never set v4l2_buffer.field to ANY
    ^C<Ctrl + C> pressed.
    --- Ran for 3.394 seconds
    
    Done.
    
    [064bit prompt]> 
    [064bit prompt]> # ---------- CUSTOM (NO) LOG ----------
    [064bit prompt]> GST_DEBUG=2 LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:$(pwd)" python ./code01.py 2
    Python 3.8.10 (default, Jun 22 2022, 20:18:18) [GCC 9.4.0] 064bit on linux
    
    ---
    Custom log function info: 2
    Running pipeline:
      gst-launch-1.0 -e videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0
    ---
    
    ^C<Ctrl + C> pressed.
    --- Ran for 2.515 seconds
    
    Done.
    

    Bingo !!!

    I let it run for a couple of minutes with a greater level (DEBUG, 5) and it performed fine. By no means I conclude that it passed the time test (must check how it behaves when running for days), but definitely it's a start.

    2. Log via a Python extension module

    It's a more elaborate form of the previous, with a couple of key differences:

    1. Functionally: The function adding the log callback will need to receive another argument, and that is the Python function that the callback will have to call. So, the information from GStreamer objects will be extracted by the callback (after all, text is being logged, not objects), and will be passed to the Python function (via [Python.Docs]: Python/C API Reference Manual) as plain Python objects, thus bypassing PyGObject

    2. Structurally: It's a "special" .dll following Python module protocol ([Python.Docs]: Extending and Embedding the Python Interpreter). I don't think it's mandatory to be so (can be a regular .dll), but CTypes can be confusing for some users (with no low level background), and also a bunch of conversions are automatically performed. The drawback is that (C) boilerplate code needs to be written

    Code:

    • gstlogmodule.c:

      #define PY_SSIZE_T_CLEAN
      #include <Python.h>
      
      #include <gst/gst.h>
      
      #define FUNC_NAME "python"
      
      
      static PyObject *gPythonFunc = NULL;
      
      
      static G_GNUC_NO_INSTRUMENT
      void logFunction(
          GstDebugCategory *category,
          GstDebugLevel level,
          const gchar *file,
          const gchar *function,
          gint line,
          GObject *object,
          GstDebugMessage *message,
          gpointer user_data
      )
      {
          if (gPythonFunc == NULL) {
              //g_print("Would output too much garbage\n");
              return;
          }
          if (_Py_IsFinalizing()) {
              return;
          }
          PyGILState_STATE gstate = PyGILState_Ensure();
          if (!PyObject_CallFunction(
              gPythonFunc, "sssis(Ks)s",
              gst_debug_level_get_name(level), category->name,
              file, line, function, (uintptr_t)object,
              G_IS_OBJECT(object) ? G_OBJECT_TYPE_NAME(object) : "",
              gst_debug_message_get(message)
          )) {
              g_print("PyObject_CallFunction\n");
              PyErr_Clear();
          }
          PyGILState_Release(gstate);
      }
      
      
      static void clear()
      {
          gst_debug_remove_log_function(logFunction);
          Py_XDECREF(gPythonFunc);
          gPythonFunc = NULL;
      }
      
      
      static PyObject* pySetLogFunction(PyObject *self, PyObject *args)
      {
          char *info = NULL;
          int removeDefault = 0;
          PyObject *pythonFunc = NULL;
          if(!PyArg_ParseTuple(args, "siO", &info, &removeDefault, &pythonFunc)) {
              return NULL;
          }
          if (!PyCallable_Check(pythonFunc)) {
              PyErr_SetString(PyExc_ValueError, "Invalid Python function (callable)");
              return NULL;
          }
          if (removeDefault) {
              gst_debug_remove_log_function(NULL);
          }
          if (!strcmp(info, FUNC_NAME)) {
              clear();
              Py_INCREF(pythonFunc);
              gPythonFunc = pythonFunc;
              gst_debug_add_log_function(logFunction, NULL, NULL);
          }
          Py_RETURN_NONE;
      }
      
      
      static PyObject* pyClearLogFunction(PyObject *self, PyObject *args)
      {
          char *info = NULL;
          int addDefault = 0;
          if(!PyArg_ParseTuple(args, "si", &info, &addDefault)) {
              return NULL;
          }
          if ((gPythonFunc != NULL) && (!strcmp(info, FUNC_NAME))) {
              clear();
          }
          if (addDefault) {
              gst_debug_add_log_function(NULL, NULL, NULL);
          }
          Py_RETURN_NONE;
      }
      
      
      static PyMethodDef gstLogMethods[] = {
          { "set_log_function", pySetLogFunction, METH_VARARGS, "Set log function" },
          { "clear_log_function", pyClearLogFunction, METH_VARARGS, "Clear log function" },
          { NULL, NULL, 0, NULL }
      };
      
      
      static struct PyModuleDef gstLogModule = {
          PyModuleDef_HEAD_INIT, "gstlog", "GStreamer logging attempt (https://stackoverflow.com/a/77933740/4788546)", -1, gstLogMethods
      };
      
      
      PyMODINIT_FUNC
      PyInit_gstlog()
      {
          Py_AtExit(clear);
          return PyModule_Create(&gstLogModule);
      }
      
    • code02.py:

      #!/usr/bin/env python
      
      import sys
      import time
      from typing import Tuple
      
      import gi
      
      import gstlog
      from code00 import pipeline_string, run_pipeline
      
      gi.require_version("Gst", "1.0")
      from gi.repository import Gst
      
      
      def log_function(category: str, level: str,
                       file: str, line: int, function: str,
                       obj_data: Tuple[int, str], message: str):
          print(f"PY {level} {category} - {file}:{line}:{function}:"
                f"<0x{obj_data[0]:x} ({obj_data[1]})> {message}")
      
      
      def main(*argv):
          custom_log = bool(argv)  # Custom log if any argument passed
          print(f"---\nCustom log function: {custom_log}\nRunning pipeline:\n  gst-launch-1.0 -e {pipeline_string}\n---\n")
      
          if custom_log:
              gstlog.set_log_function("python", 1, log_function)
          Gst.init(None)
          run_pipeline()
      
      
      if __name__ == "__main__":
          print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                         64 if sys.maxsize > 0x100000000 else 32, sys.platform))
          rc = main(*sys.argv[1:])
          print("\nDone.\n")
          sys.exit(rc)
      

    As a note, I couldn't reuse previous C code, so there's some duplication.

    Output:

    [064bit prompt]> ls
    code00.py  code01.py  code02.py  gstlogmodule.c  liblogfunctions.so  logfunctions.c  logfunctions.h  main00  main00.c
    [064bit prompt]> gcc -Wall -g -O2 -fPIC -DPIC -pthread -I/usr/include/gstreamer-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/python3.8 -Wl,-no-undefined -shared -o ./gstlog.cpython-38-x86_64-linux-gnu.so ./gstlogmodule.c -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0 -lpython3.8
    [064bit prompt]> ls
    code00.py  code01.py  code02.py  gstlog.cpython-38-x86_64-linux-gnu.so  gstlogmodule.c  liblogfunctions.so  logfunctions.c  logfunctions.h  main00  main00.c
    [064bit prompt]> 
    [064bit prompt]> # ---------- DEFAULT LOG ----------
    [064bit prompt]> GST_DEBUG=2 python ./code02.py
    Python 3.8.10 (default, Jun 22 2022, 20:18:18) [GCC 9.4.0] 064bit on linux
    
    ---
    Custom log function: False
    Running pipeline:
      gst-launch-1.0 -e videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0
    ---
    
    0:00:00.034991772 2679991      0x1726d50 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:sink> Unable to try format: Unknown error -1
    0:00:00.035011669 2679991      0x1726d50 WARN                    v4l2 gstv4l2object.c:2942:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:sink> Could not probe minimum capture size for pixelformat YM12
    0:00:00.035018896 2679991      0x1726d50 WARN                    v4l2 gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<nvv4l2h264enc0:sink> Unable to try format: Unknown error -1
    0:00:00.035025451 2679991      0x1726d50 WARN                    v4l2 gstv4l2object.c:2948:gst_v4l2_object_probe_caps_for_format:<nvv4l2h264enc0:sink> Could not probe maximum capture size for pixelformat YM12
    0:00:00.035032820 2679991      0x1726d50 WARN                    v4l2 gstv4l2object.c:2395:gst_v4l2_object_add_interlace_mode:0x1729cf0 Failed to determine interlace mode
    
    # @TODO - cfati: Truncated output
    
    0:00:00.095393439 2679991      0x14201e0 WARN          v4l2bufferpool gstv4l2bufferpool.c:1082:gst_v4l2_buffer_pool_start:<nvv4l2h264enc0:pool:src> Uncertain or not enough buffers, enabling copy threshold
    0:00:00.100478795 2679991      0x173a460 WARN          v4l2bufferpool gstv4l2bufferpool.c:1533:gst_v4l2_buffer_pool_dqbuf:<nvv4l2h264enc0:pool:src> Driver should never set v4l2_buffer.field to ANY
    ^C<Ctrl + C> pressed.
    --- Ran for 2.205 seconds
    
    Done.
    
    [064bit prompt]> 
    [064bit prompt]> # ---------- CUSTOM LOG ----------
    [064bit prompt]> GST_DEBUG=2 python ./code02.py 1
    Python 3.8.10 (default, Jun 22 2022, 20:18:18) [GCC 9.4.0] 064bit on linux
    
    ---
    Custom log function: True
    Running pipeline:
      gst-launch-1.0 -e videotestsrc pattern=18 ! nvvideoconvert ! nvv4l2h264enc ! fakesink sync=0
    ---
    
    PY v4l2 WARN    - gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<0x27027d0 (GstPad)> Unable to try format: Unknown error -1
    PY v4l2 WARN    - gstv4l2object.c:2942:gst_v4l2_object_probe_caps_for_format:<0x27027d0 (GstPad)> Could not probe minimum capture size for pixelformat YM12
    PY v4l2 WARN    - gstv4l2object.c:3057:gst_v4l2_object_get_nearest_size:<0x27027d0 (GstPad)> Unable to try format: Unknown error -1
    PY v4l2 WARN    - gstv4l2object.c:2948:gst_v4l2_object_probe_caps_for_format:<0x27027d0 (GstPad)> Could not probe maximum capture size for pixelformat YM12
    PY v4l2 WARN    - gstv4l2object.c:2395:gst_v4l2_object_add_interlace_mode:<0x27f44f0 (None)> Failed to determine interlace mode
    
    # @TODO - cfati: Truncated output
    
    PY v4l2bufferpool WARN    - gstv4l2bufferpool.c:1082:gst_v4l2_buffer_pool_start:<0x7fb6fc010090 (GstNvV4l2BufferPool)> Uncertain or not enough buffers, enabling copy threshold
    PY v4l2bufferpool WARN    - gstv4l2bufferpool.c:1533:gst_v4l2_buffer_pool_dqbuf:<0x7fb6fc010090 (GstNvV4l2BufferPool)> Driver should never set v4l2_buffer.field to ANY
    ^C<Ctrl + C> pressed.
    --- Ran for 3.423 seconds
    
    Done.
    

    That's it! 👍

    3. Identify the bug and fix it

    The above variant(s), simply work around the actual problem (wherever it might be).

    The right way would be to identify and fix it, but that seems a task for Sisyphus, and besides it's no longer reproducible in newer versions (GStreamer 1.20 from Nvidia's DeepStream 6.4) as stated in [FreeDesktop.GitLab]: GStreamer/GStreamer - Collecting GStreamer logs in Python ends with Segmentation fault.

    4. Conclusion

    Since:

    • It's fixed in newer versions

    • There is an acceptable enough workaround for older ones

    this matter can be safely closed.