From 8ff68fb176c40c52e4f41460d6ac878aa4900e0c Mon Sep 17 00:00:00 2001 From: Avi Zimmerman Date: Thu, 7 Jan 2021 01:46:59 +0200 Subject: [PATCH] improve logging --- examples/plugins/filesrc/filesrc.go | 37 +++++---- gst/g_parameter_spec.go | 38 +-------- gst/gst_debug.go | 117 ++++++++++++++++++++++++++++ gst/gst_element.go | 12 +-- gst/gst_object.go | 16 ++-- 5 files changed, 154 insertions(+), 66 deletions(-) create mode 100644 gst/gst_debug.go diff --git a/examples/plugins/filesrc/filesrc.go b/examples/plugins/filesrc/filesrc.go index 78ecd7d..6060851 100644 --- a/examples/plugins/filesrc/filesrc.go +++ b/examples/plugins/filesrc/filesrc.go @@ -28,6 +28,13 @@ import ( "github.com/tinyzimmer/go-gst/gst/base" ) +// CAT is the log category for the gofilesrc +var CAT = gst.NewDebugCategory( + "gofilesrc", + gst.DebugColorNone, + "GoFileSrc Element", +) + // Here we define a list of ParamSpecs that will make up the properties for our element. // This element only has a single property, the location of the file to read from. // When getting and setting properties later on, you will reference them by their index in @@ -152,12 +159,12 @@ func (f *fileSrc) SetProperty(self *gst.Object, id uint, value *glib.Value) { val, _ = value.GetString() } if err := f.setLocation(val); err != nil { - gst.ToElement(self).Error(gst.DomainLibrary, gst.LibraryErrorSettings, + gst.ToElement(self).ErrorMessage(gst.DomainLibrary, gst.LibraryErrorSettings, "Could not set location on object", err.Error(), ) } - gst.ToElement(self).Info(gst.DomainLibrary, fmt.Sprintf("Set location to %s", f.settings.location)) + self.Log(CAT, gst.LevelInfo, fmt.Sprintf("Set location to %s", f.settings.location)) } } @@ -174,7 +181,7 @@ func (f *fileSrc) GetProperty(self *gst.Object, id uint) *glib.Value { if err == nil { return val } - gst.ToElement(self).Error(gst.DomainLibrary, gst.LibraryErrorSettings, + gst.ToElement(self).ErrorMessage(gst.DomainLibrary, gst.LibraryErrorSettings, fmt.Sprintf("Could not convert %s to GValue", f.settings.location), err.Error(), ) @@ -203,7 +210,7 @@ func (f *fileSrc) GetSize(self *base.GstBaseSrc) (bool, int64) { stat, err := f.state.file.Stat() if err != nil { // This should never happen - self.Error(gst.DomainResource, gst.ResourceErrorFailed, + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorFailed, "Could not retrieve fileinfo on opened file", err.Error(), ) @@ -216,19 +223,19 @@ func (f *fileSrc) GetSize(self *base.GstBaseSrc) (bool, int64) { // and any error encountered in the process is posted to the pipeline. func (f *fileSrc) Start(self *base.GstBaseSrc) bool { if f.state.started { - self.Error(gst.DomainResource, gst.ResourceErrorSettings, "FileSrc is already started", "") + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorSettings, "FileSrc is already started", "") return false } if f.settings.location == "" { - self.Error(gst.DomainResource, gst.ResourceErrorSettings, "File location is not defined", "") + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorSettings, "File location is not defined", "") return false } var err error f.state.file, err = os.OpenFile(f.settings.location, syscall.O_RDONLY, 0444) if err != nil { - self.Error(gst.DomainResource, gst.ResourceErrorOpenRead, + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorOpenRead, fmt.Sprintf("Could not open file %s for reading", f.settings.location), err.Error()) return false } @@ -238,19 +245,19 @@ func (f *fileSrc) Start(self *base.GstBaseSrc) bool { self.StartComplete(gst.FlowOK) - self.Info(gst.DomainResource, "Started") + self.Log(CAT, gst.LevelInfo, "Started") return true } // Stop is called to stop the element. The file is closed and the local values are zeroed out. func (f *fileSrc) Stop(self *base.GstBaseSrc) bool { if !f.state.started { - self.Error(gst.DomainResource, gst.ResourceErrorSettings, "FileSrc is not started", "") + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorSettings, "FileSrc is not started", "") return false } if err := f.state.file.Close(); err != nil { - self.Error(gst.DomainResource, gst.ResourceErrorClose, "Failed to close the source file", err.Error()) + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorClose, "Failed to close the source file", err.Error()) return false } @@ -258,7 +265,7 @@ func (f *fileSrc) Stop(self *base.GstBaseSrc) bool { f.state.position = 0 f.state.started = false - self.Info(gst.DomainResource, "Stopped") + self.Log(CAT, gst.LevelInfo, "Stopped") return true } @@ -267,13 +274,13 @@ func (f *fileSrc) Stop(self *base.GstBaseSrc) bool { // where we currently are in the file. This is why we store the position in the file locally. func (f *fileSrc) Fill(self *base.GstBaseSrc, offset uint64, size uint, buffer *gst.Buffer) gst.FlowReturn { if !f.state.started || f.state.file == nil { - self.Error(gst.DomainCore, gst.CoreErrorFailed, "Not started yet", "") + self.ErrorMessage(gst.DomainCore, gst.CoreErrorFailed, "Not started yet", "") return gst.FlowError } if f.state.position != offset { if _, err := f.state.file.Seek(int64(offset), 0); err != nil { - self.Error(gst.DomainResource, gst.ResourceErrorSeek, + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorSeek, fmt.Sprintf("Failed to seek to %d in file", offset), err.Error()) return gst.FlowError } @@ -282,7 +289,7 @@ func (f *fileSrc) Fill(self *base.GstBaseSrc, offset uint64, size uint, buffer * out := make([]byte, int(size)) if _, err := f.state.file.Read(out); err != nil && err != io.EOF { - self.Error(gst.DomainResource, gst.ResourceErrorRead, + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorRead, fmt.Sprintf("Failed to read %d bytes from file at %d", size, offset), err.Error()) return gst.FlowError } @@ -291,7 +298,7 @@ func (f *fileSrc) Fill(self *base.GstBaseSrc, offset uint64, size uint, buffer * bufmap := buffer.Map(gst.MapWrite) if bufmap == nil { - self.Error(gst.DomainLibrary, gst.LibraryErrorFailed, "Failed to map buffer", "") + self.ErrorMessage(gst.DomainLibrary, gst.LibraryErrorFailed, "Failed to map buffer", "") return gst.FlowError } defer buffer.Unmap() diff --git a/gst/g_parameter_spec.go b/gst/g_parameter_spec.go index 6701ac2..631cfa1 100644 --- a/gst/g_parameter_spec.go +++ b/gst/g_parameter_spec.go @@ -12,22 +12,14 @@ import ( // ParameterSpec is a go representation of a C GParamSpec type ParameterSpec struct { - paramSpec *C.GParamSpec - defaultValue *glib.Value + paramSpec *C.GParamSpec } // NewStringParameter returns a new ParameterSpec that will hold a string value. func NewStringParameter(name, nick, blurb string, defaultValue *string, flags ParameterFlags) *ParameterSpec { var cdefault *C.gchar - var paramDefault *glib.Value if defaultValue != nil { cdefault = C.CString(*defaultValue) - var err error - paramDefault, err = glib.ValueInit(glib.TYPE_STRING) - if err != nil { - return nil - } - paramDefault.SetString(*defaultValue) } paramSpec := C.g_param_spec_string( (*C.gchar)(C.CString(name)), @@ -36,7 +28,7 @@ func NewStringParameter(name, nick, blurb string, defaultValue *string, flags Pa (*C.gchar)(cdefault), C.GParamFlags(flags), ) - return &ParameterSpec{paramSpec: paramSpec, defaultValue: paramDefault} + return &ParameterSpec{paramSpec: paramSpec} } // Name returns the name of this parameter. @@ -64,12 +56,6 @@ func (p *ParameterSpec) OwnerType() glib.Type { return glib.Type(p.paramSpec.owner_type) } -// DefaultValue returns the default value for the parameter if it was included when the object -// was instantiated. Otherwise it returns nil. -func (p *ParameterSpec) DefaultValue() *glib.Value { - return p.defaultValue -} - // Unref the underlying paramater spec. func (p *ParameterSpec) Unref() { C.g_param_spec_unref(p.paramSpec) } @@ -161,14 +147,6 @@ type FlagsValue struct { ValueName, ValueNick string } -// GetDefaultFlags returns the default flags for this parameter spec. -func (p *ParameterSpec) GetDefaultFlags() int { - if p.DefaultValue() == nil { - return 0 - } - return int(C.g_value_get_flags((*C.GValue)(p.DefaultValue().Native()))) -} - // GetFlagValues returns the possible flags for this parameter. func (p *ParameterSpec) GetFlagValues() []*FlagsValue { var gSize C.guint @@ -185,18 +163,6 @@ func (p *ParameterSpec) GetFlagValues() []*FlagsValue { return out } -// GetCaps returns the caps in this parameter if it is of type GST_TYPE_CAPS. -func (p *ParameterSpec) GetCaps() *Caps { - if p.DefaultValue() == nil { - return nil - } - caps := C.gst_value_get_caps((*C.GValue)(unsafe.Pointer(p.DefaultValue().Native()))) - if caps == nil { - return nil - } - return wrapCaps(caps) -} - // ParameterFlags is a go cast of GParamFlags. type ParameterFlags int diff --git a/gst/gst_debug.go b/gst/gst_debug.go new file mode 100644 index 0000000..a7a1c68 --- /dev/null +++ b/gst/gst_debug.go @@ -0,0 +1,117 @@ +package gst + +/* +#include "gst.go.h" + +void cgoDebugLog (GstDebugCategory * category, + GstDebugLevel level, + const gchar * file, + const gchar * function, + gint line, + GObject * object, + const gchar * msg) +{ + gst_debug_log(category, level, file, function, line, object, msg); +} + +*/ +import "C" +import ( + "path" + "runtime" +) + +// DebugColorFlags are terminal style flags you can use when creating your debugging +// categories to make them stand out in debugging output. +type DebugColorFlags int + +// Type castings of DebugColorFlags +const ( + DebugColorNone DebugColorFlags = 0 // (0) - No color + DebugColorFgBlack DebugColorFlags = C.GST_DEBUG_FG_BLACK // (0) – Use black as foreground color. + DebugColorFgRed DebugColorFlags = C.GST_DEBUG_FG_RED // (1) – Use red as foreground color. + DebugColorFgGreen DebugColorFlags = C.GST_DEBUG_FG_GREEN // (2) – Use green as foreground color. + DebugColorFgYellow DebugColorFlags = C.GST_DEBUG_FG_YELLOW // (3) – Use yellow as foreground color. + DebugColorFgBlue DebugColorFlags = C.GST_DEBUG_FG_BLUE // (4) – Use blue as foreground color. + DebugColorFgMagenta DebugColorFlags = C.GST_DEBUG_FG_MAGENTA // (5) – Use magenta as foreground color. + DebugColorFgCyan DebugColorFlags = C.GST_DEBUG_FG_CYAN // (6) – Use cyan as foreground color. + DebugColorFgWhite DebugColorFlags = C.GST_DEBUG_FG_WHITE // (7) – Use white as foreground color. + DebugColorBgBlack DebugColorFlags = C.GST_DEBUG_BG_BLACK // (0) – Use black as background color. + DebugColorBgRed DebugColorFlags = C.GST_DEBUG_BG_RED // (16) – Use red as background color. + DebugColorBgGreen DebugColorFlags = C.GST_DEBUG_BG_GREEN // (32) – Use green as background color. + DebugColorBgYellow DebugColorFlags = C.GST_DEBUG_BG_YELLOW // (48) – Use yellow as background color. + DebugColorBgBlue DebugColorFlags = C.GST_DEBUG_BG_BLUE // (64) – Use blue as background color. + DebugColorBgMagenta DebugColorFlags = C.GST_DEBUG_BG_MAGENTA // (80) – Use magenta as background color. + DebugColorBgCyan DebugColorFlags = C.GST_DEBUG_BG_CYAN // (96) – Use cyan as background color. + DebugColorBgWhite DebugColorFlags = C.GST_DEBUG_BG_WHITE // (112) – Use white as background color. + DebugColorBold DebugColorFlags = C.GST_DEBUG_BOLD // (256) – Make the output bold. + DebugColorUnderline DebugColorFlags = C.GST_DEBUG_UNDERLINE // (512) – Underline the output. +) + +// DebugColorMode represents how to display colors. +type DebugColorMode int + +// Type castings of DebugColorModes +const ( + DebugColorModeOff DebugColorMode = C.GST_DEBUG_COLOR_MODE_OFF // (0) – Do not use colors in logs. + DebugColorModeOn DebugColorMode = C.GST_DEBUG_COLOR_MODE_ON // (1) – Paint logs in a platform-specific way. + DebugColorModeUnix DebugColorMode = C.GST_DEBUG_COLOR_MODE_UNIX // (2) – Paint logs with UNIX terminal color codes no matter what platform GStreamer is running on. +) + +// DebugLevel defines the importance of a debugging message. The more important a message is, the +// greater the probability that the debugging system outputs it. +type DebugLevel int + +// Type castings of DebugLevels +const ( + LevelNone DebugLevel = C.GST_LEVEL_NONE // (0) – No debugging level specified or desired. Used to deactivate debugging output. + LevelError DebugLevel = C.GST_LEVEL_ERROR // (1) – Error messages are to be used only when an error occurred that stops the application from keeping working correctly. An examples is gst_element_error, which outputs a message with this priority. It does not mean that the application is terminating as with g_error. + LevelWarning DebugLevel = C.GST_LEVEL_WARNING // (2) – Warning messages are to inform about abnormal behaviour that could lead to problems or weird behaviour later on. An example of this would be clocking issues ("your computer is pretty slow") or broken input data ("Can't synchronize to stream.") + LevelFixMe DebugLevel = C.GST_LEVEL_FIXME // (3) – Fixme messages are messages that indicate that something in the executed code path is not fully implemented or handled yet. Note that this does not replace proper error handling in any way, the purpose of this message is to make it easier to spot incomplete/unfinished pieces of code when reading the debug log. + LevelInfo DebugLevel = C.GST_LEVEL_INFO // (4) – Informational messages should be used to keep the developer updated about what is happening. Examples where this should be used are when a typefind function has successfully determined the type of the stream or when an mp3 plugin detects the format to be used. ("This file has mono sound.") + LevelDebug DebugLevel = C.GST_LEVEL_DEBUG // (5) – Debugging messages should be used when something common happens that is not the expected default behavior, or something that's useful to know but doesn't happen all the time (ie. per loop iteration or buffer processed or event handled). An example would be notifications about state changes or receiving/sending of events. + LevelLog DebugLevel = C.GST_LEVEL_LOG // (6) – Log messages are messages that are very common but might be useful to know. As a rule of thumb a pipeline that is running as expected should never output anything else but LOG messages whilst processing data. Use this log level to log recurring information in chain functions and loop functions, for example. + LevelTrace DebugLevel = C.GST_LEVEL_TRACE // (7) – Tracing-related messages. Examples for this are referencing/dereferencing of objects. + LevelMemDump DebugLevel = C.GST_LEVEL_MEMDUMP // (9) – memory dump messages are used to log (small) chunks of data as memory dumps in the log. They will be displayed as hexdump with ASCII characters. +) + +// StackTraceFlags are flags for configuring stack traces +type StackTraceFlags int + +// Type castings of StackTraceFlags +const ( + StackTraceShowNone StackTraceFlags = C.GST_STACK_TRACE_SHOW_NONE // (0) – Try to retrieve the minimum information available, which may be none on some platforms (Since: 1.18) + StackTraceShowFull StackTraceFlags = C.GST_STACK_TRACE_SHOW_FULL // (1) – Try to retrieve as much information as possible, including source information when getting the stack trace +) + +// DebugCategory is a struct that describes a category of log messages. +type DebugCategory struct { + ptr *C.GstDebugCategory +} + +// NewDebugCategory initializes a new DebugCategory with the given properties and set +// to the default threshold. +func NewDebugCategory(name string, color DebugColorFlags, description string) *DebugCategory { + cat := C._gst_debug_category_new(C.CString(name), C.guint(color), C.CString(description)) + return &DebugCategory{ptr: cat} +} + +// Log logs the given message using the currently registered debugging handlers. You can optionally +// provide a single object to log the message for. GStreamer will automatically add a newline to the +// end of the message. +func (d *DebugCategory) Log(level DebugLevel, message string, obj ...*Object) { + var o *C.GObject + if len(obj) > 0 { + o = (*C.GObject)(obj[0].Unsafe()) + } + function, file, line, _ := runtime.Caller(1) + C.cgoDebugLog( + d.ptr, + C.GstDebugLevel(level), + C.CString(path.Base(file)), + (C.CString(runtime.FuncForPC(function).Name())), + C.gint(line), + o, + C.CString(message), + ) +} diff --git a/gst/gst_element.go b/gst/gst_element.go index e320d92..5235e38 100644 --- a/gst/gst_element.go +++ b/gst/gst_element.go @@ -147,23 +147,23 @@ func (e *Element) Emit(signal string, args ...interface{}) (interface{}, error) return e.Object.Emit(signal, args...) } -// Info is a convenience wrapper for posting an info message from inside an element. Only to be used from +// InfoMessage is a convenience wrapper for posting an info message from inside an element. Only to be used from // plugins. -func (e *Element) Info(domain Domain, text string) { +func (e *Element) InfoMessage(domain Domain, text string) { function, file, line, _ := runtime.Caller(1) e.MessageFull(MessageInfo, domain, ErrorCode(0), "", text, path.Base(file), runtime.FuncForPC(function).Name(), line) } -// Warning is a convenience wrapper for posting a warning message from inside an element. Only to be used from +// WarningMessage is a convenience wrapper for posting a warning message from inside an element. Only to be used from // plugins. -func (e *Element) Warning(domain Domain, text string) { +func (e *Element) WarningMessage(domain Domain, text string) { function, file, line, _ := runtime.Caller(1) e.MessageFull(MessageWarning, domain, ErrorCode(0), "", text, path.Base(file), runtime.FuncForPC(function).Name(), line) } -// Error is a convenience wrapper for posting an error message from inside an element. Only to be used from +// ErrorMessage is a convenience wrapper for posting an error message from inside an element. Only to be used from // plugins. -func (e *Element) Error(domain Domain, code ErrorCode, text, debug string) { +func (e *Element) ErrorMessage(domain Domain, code ErrorCode, text, debug string) { function, file, line, _ := runtime.Caller(1) e.MessageFull(MessageError, domain, code, text, debug, path.Base(file), runtime.FuncForPC(function).Name(), line) } diff --git a/gst/gst_object.go b/gst/gst_object.go index 81d72e6..df94775 100644 --- a/gst/gst_object.go +++ b/gst/gst_object.go @@ -69,18 +69,16 @@ func (o *Object) ListProperties() []*ParameterSpec { defer C.g_free((C.gpointer)(props)) out := make([]*ParameterSpec, 0) for _, prop := range (*[1 << 30]*C.GParamSpec)(unsafe.Pointer(props))[:size:size] { - var gval C.GValue - flags := ParameterFlags(prop.flags) - if flags.Has(ParameterReadable) { - C.g_object_get_property((*C.GObject)(o.Unsafe()), prop.name, &gval) - } else { - C.g_param_value_set_default((*C.GParamSpec)(prop), &gval) - } C.g_param_spec_sink(prop) // steal the ref on the property out = append(out, &ParameterSpec{ - paramSpec: prop, - defaultValue: glib.ValueFromNative(unsafe.Pointer(&gval)), + paramSpec: prop, }) } return out } + +// Log logs a message to the given category from this object using the currently registered +// debugging handlers. +func (o *Object) Log(cat *DebugCategory, level DebugLevel, message string) { + cat.Log(level, message, o) +}